1 use crate::{api::claims::Claims, LemmyContext};
2 use actix_web::{error::ErrorBadRequest, *};
4 use chrono::{DateTime, NaiveDateTime, Utc};
5 use diesel::PgConnection;
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 rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
19 use serde::Deserialize;
20 use std::str::FromStr;
21 use strum::ParseError;
23 #[derive(Deserialize)]
35 pub fn config(cfg: &mut web::ServiceConfig) {
37 .route("/feeds/{type}/{name}.xml", web::get().to(get_feed))
38 .route("/feeds/all.xml", web::get().to(get_all_feed));
41 async fn get_all_feed(
42 info: web::Query<Params>,
43 context: web::Data<LemmyContext>,
44 ) -> Result<HttpResponse, Error> {
45 let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
47 let rss = blocking(context.pool(), move |conn| {
48 get_feed_all_data(conn, &sort_type)
51 .map_err(ErrorBadRequest)?;
55 .content_type("application/rss+xml")
60 fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, LemmyError> {
61 let site_view = SiteView::read(&conn)?;
63 let posts = PostQueryBuilder::create(&conn)
64 .listing_type(ListingType::All)
68 let items = create_post_items(posts)?;
70 let mut channel_builder = ChannelBuilder::default();
72 .title(&format!("{} - All", site_view.name))
73 .link(format!("https://{}", Settings::get().hostname))
76 if let Some(site_desc) = site_view.description {
77 channel_builder.description(&site_desc);
80 Ok(channel_builder.build().map_err(|e| anyhow!(e))?.to_string())
84 web::Path((req_type, param)): web::Path<(String, String)>,
85 info: web::Query<Params>,
86 context: web::Data<LemmyContext>,
87 ) -> Result<HttpResponse, Error> {
88 let sort_type = get_sort_type(info).map_err(ErrorBadRequest)?;
90 let request_type = match req_type.as_str() {
91 "u" => RequestType::User,
92 "c" => RequestType::Community,
93 "front" => RequestType::Front,
94 "inbox" => RequestType::Inbox,
95 _ => return Err(ErrorBadRequest(LemmyError::from(anyhow!("wrong_type")))),
98 let builder = blocking(context.pool(), move |conn| match request_type {
99 RequestType::User => get_feed_user(conn, &sort_type, param),
100 RequestType::Community => get_feed_community(conn, &sort_type, param),
101 RequestType::Front => get_feed_front(conn, &sort_type, param),
102 RequestType::Inbox => get_feed_inbox(conn, param),
105 .map_err(ErrorBadRequest)?;
107 let rss = builder.build().map_err(ErrorBadRequest)?.to_string();
111 .content_type("application/rss+xml")
116 fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
117 let sort_query = info
120 .unwrap_or_else(|| SortType::Hot.to_string());
121 SortType::from_str(&sort_query)
126 sort_type: &SortType,
128 ) -> Result<ChannelBuilder, LemmyError> {
129 let site_view = SiteView::read(&conn)?;
130 let user = User_::find_by_username(&conn, &user_name)?;
131 let user_url = user.get_profile_url(&Settings::get().hostname);
133 let posts = PostQueryBuilder::create(&conn)
134 .listing_type(ListingType::All)
136 .for_creator_id(user.id)
139 let items = create_post_items(posts)?;
141 let mut channel_builder = ChannelBuilder::default();
143 .title(&format!("{} - {}", site_view.name, user.name))
150 fn get_feed_community(
152 sort_type: &SortType,
153 community_name: String,
154 ) -> Result<ChannelBuilder, LemmyError> {
155 let site_view = SiteView::read(&conn)?;
156 let community = Community::read_from_name(&conn, &community_name)?;
158 let posts = PostQueryBuilder::create(&conn)
159 .listing_type(ListingType::All)
161 .for_community_id(community.id)
164 let items = create_post_items(posts)?;
166 let mut channel_builder = ChannelBuilder::default();
168 .title(&format!("{} - {}", site_view.name, community.name))
169 .link(community.actor_id)
172 if let Some(community_desc) = community.description {
173 channel_builder.description(&community_desc);
181 sort_type: &SortType,
183 ) -> Result<ChannelBuilder, LemmyError> {
184 let site_view = SiteView::read(&conn)?;
185 let user_id = Claims::decode(&jwt)?.claims.id;
187 let posts = PostQueryBuilder::create(&conn)
188 .listing_type(ListingType::Subscribed)
193 let items = create_post_items(posts)?;
195 let mut channel_builder = ChannelBuilder::default();
197 .title(&format!("{} - Subscribed", site_view.name))
198 .link(format!("https://{}", Settings::get().hostname))
201 if let Some(site_desc) = site_view.description {
202 channel_builder.description(&site_desc);
208 fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, LemmyError> {
209 let site_view = SiteView::read(&conn)?;
210 let user_id = Claims::decode(&jwt)?.claims.id;
212 let sort = SortType::New;
214 let replies = ReplyQueryBuilder::create(&conn, user_id)
218 let mentions = UserMentionQueryBuilder::create(&conn, user_id)
222 let items = create_reply_and_mention_items(replies, mentions)?;
224 let mut channel_builder = ChannelBuilder::default();
226 .title(&format!("{} - Inbox", site_view.name))
227 .link(format!("https://{}/inbox", Settings::get().hostname))
230 if let Some(site_desc) = site_view.description {
231 channel_builder.description(&site_desc);
237 fn create_reply_and_mention_items(
238 replies: Vec<ReplyView>,
239 mentions: Vec<UserMentionView>,
240 ) -> Result<Vec<Item>, LemmyError> {
241 let mut reply_items: Vec<Item> = replies
244 let reply_url = format!(
245 "https://{}/post/{}/comment/{}",
246 Settings::get().hostname,
250 build_item(&r.creator_name, &r.published, &reply_url, &r.content)
252 .collect::<Result<Vec<Item>, LemmyError>>()?;
254 let mut mention_items: Vec<Item> = mentions
257 let mention_url = format!(
258 "https://{}/post/{}/comment/{}",
259 Settings::get().hostname,
263 build_item(&m.creator_name, &m.published, &mention_url, &m.content)
265 .collect::<Result<Vec<Item>, LemmyError>>()?;
267 reply_items.append(&mut mention_items);
273 published: &NaiveDateTime,
276 ) -> Result<Item, LemmyError> {
277 let mut i = ItemBuilder::default();
278 i.title(format!("Reply from {}", creator_name));
279 let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name);
281 "/u/{} <a href=\"{}\">(link)</a>",
282 creator_name, author_url
284 let dt = DateTime::<Utc>::from_utc(*published, Utc);
285 i.pub_date(dt.to_rfc2822());
286 i.comments(url.to_owned());
287 let guid = GuidBuilder::default()
291 .map_err(|e| anyhow!(e))?;
293 i.link(url.to_owned());
295 let html = markdown_to_html(&content.to_string());
297 Ok(i.build().map_err(|e| anyhow!(e))?)
300 fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
301 let mut items: Vec<Item> = Vec::new();
304 let mut i = ItemBuilder::default();
308 let author_url = format!("https://{}/u/{}", Settings::get().hostname, p.creator_name);
310 "/u/{} <a href=\"{}\">(link)</a>",
311 p.creator_name, author_url
314 let dt = DateTime::<Utc>::from_utc(p.published, Utc);
315 i.pub_date(dt.to_rfc2822());
317 let post_url = format!("https://{}/post/{}", Settings::get().hostname, p.id);
318 i.comments(post_url.to_owned());
319 let guid = GuidBuilder::default()
323 .map_err(|e| anyhow!(e))?;
326 let community_url = format!(
328 Settings::get().hostname,
332 let category = CategoryBuilder::default()
334 "/c/{} <a href=\"{}\">(link)</a>",
335 p.community_name, community_url
337 .domain(Settings::get().hostname.to_owned())
339 .map_err(|e| anyhow!(e))?;
341 i.categories(vec![category]);
343 if let Some(url) = p.url {
348 let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
355 p.number_of_comments);
357 if let Some(body) = p.body {
358 let html = markdown_to_html(&body);
359 description.push_str(&html);
362 i.description(description);
364 items.push(i.build().map_err(|e| anyhow!(e))?);