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(format!("https://{}", Settings::get().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(format!("https://{}", Settings::get().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))
228 .link(format!("https://{}/inbox", Settings::get().hostname))
231 if let Some(site_desc) = site_view.description {
232 channel_builder.description(&site_desc);
238 fn create_reply_and_mention_items(
239 replies: Vec<ReplyView>,
240 mentions: Vec<UserMentionView>,
241 ) -> Result<Vec<Item>, LemmyError> {
242 let mut reply_items: Vec<Item> = replies
245 let reply_url = format!(
246 "https://{}/post/{}/comment/{}",
247 Settings::get().hostname,
251 build_item(&r.creator_name, &r.published, &reply_url, &r.content)
253 .collect::<Result<Vec<Item>, LemmyError>>()?;
255 let mut mention_items: Vec<Item> = mentions
258 let mention_url = format!(
259 "https://{}/post/{}/comment/{}",
260 Settings::get().hostname,
264 build_item(&m.creator_name, &m.published, &mention_url, &m.content)
266 .collect::<Result<Vec<Item>, LemmyError>>()?;
268 reply_items.append(&mut mention_items);
274 published: &NaiveDateTime,
277 ) -> Result<Item, LemmyError> {
278 let mut i = ItemBuilder::default();
279 i.title(format!("Reply from {}", creator_name));
280 let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name);
282 "/u/{} <a href=\"{}\">(link)</a>",
283 creator_name, author_url
285 let dt = DateTime::<Utc>::from_utc(*published, Utc);
286 i.pub_date(dt.to_rfc2822());
287 i.comments(url.to_owned());
288 let guid = GuidBuilder::default()
292 .map_err(|e| anyhow!(e))?;
294 i.link(url.to_owned());
296 let html = markdown_to_html(&content.to_string());
298 Ok(i.build().map_err(|e| anyhow!(e))?)
301 fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
302 let mut items: Vec<Item> = Vec::new();
305 let mut i = ItemBuilder::default();
309 let author_url = format!("https://{}/u/{}", Settings::get().hostname, p.creator_name);
311 "/u/{} <a href=\"{}\">(link)</a>",
312 p.creator_name, author_url
315 let dt = DateTime::<Utc>::from_utc(p.published, Utc);
316 i.pub_date(dt.to_rfc2822());
318 let post_url = format!("https://{}/post/{}", Settings::get().hostname, p.id);
319 i.comments(post_url.to_owned());
320 let guid = GuidBuilder::default()
324 .map_err(|e| anyhow!(e))?;
327 let community_url = format!(
329 Settings::get().hostname,
333 let category = CategoryBuilder::default()
335 "/c/{} <a href=\"{}\">(link)</a>",
336 p.community_name, community_url
338 .domain(Settings::get().hostname.to_owned())
340 .map_err(|e| anyhow!(e))?;
342 i.categories(vec![category]);
344 if let Some(url) = p.url {
349 let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
356 p.number_of_comments);
358 if let Some(body) = p.body {
359 let html = markdown_to_html(&body);
360 description.push_str(&html);
363 i.description(description);
365 items.push(i.build().map_err(|e| anyhow!(e))?);