3 fetch::fetch_remote_object,
4 get_or_fetch_and_upsert_community,
5 get_or_fetch_and_upsert_user,
16 use activitystreams::base::BaseExt;
17 use anyhow::{anyhow, Context};
18 use lemmy_db_queries::{
21 community::Community_,
23 private_message::PrivateMessage_,
28 use lemmy_db_schema::source::{
32 private_message::PrivateMessage,
35 use lemmy_db_views::{comment_view::CommentView, post_view::PostView};
36 use lemmy_db_views_actor::{community_view::CommunityView, user_view::UserViewSafe};
37 use lemmy_structs::{blocking, site::SearchResponse};
38 use lemmy_utils::{settings::Settings, LemmyError};
39 use lemmy_websocket::LemmyContext;
43 /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
45 #[derive(serde::Deserialize, Debug)]
46 enum SearchAcceptedObjects {
47 Person(Box<PersonExt>),
50 Comment(Box<NoteExt>),
53 /// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
55 /// Some working examples for use with the `docker/federation/` setup:
56 /// http://lemmy_alpha:8541/c/main, or !main@lemmy_alpha:8541
57 /// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551
58 /// http://lemmy_gamma:8561/post/3
59 /// http://lemmy_delta:8571/comment/2
60 pub async fn search_by_apub_id(
62 context: &LemmyContext,
63 ) -> Result<SearchResponse, LemmyError> {
64 // Parse the shorthand query url
65 let query_url = if query.contains('@') {
66 debug!("Search for {}", query);
67 let split = query.split('@').collect::<Vec<&str>>();
69 // User type will look like ['', username, instance]
70 // Community will look like [!community, instance]
71 let (name, instance) = if split.len() == 3 {
72 (format!("/u/{}", split[1]), split[2])
73 } else if split.len() == 2 {
74 if split[0].contains('!') {
75 let split2 = split[0].split('!').collect::<Vec<&str>>();
76 (format!("/c/{}", split2[1]), split[1])
78 return Err(anyhow!("Invalid search query: {}", query).into());
81 return Err(anyhow!("Invalid search query: {}", query).into());
86 Settings::get().get_protocol_string(),
95 let recursion_counter = &mut 0;
97 fetch_remote_object::<SearchAcceptedObjects>(context.client(), &query_url, recursion_counter)
99 if is_deleted(&fetch_response) {
100 delete_object_locally(&query_url, context).await?;
103 // Necessary because we get a stack overflow using FetchError
104 let fet_res = fetch_response.map_err(|e| LemmyError::from(e.inner))?;
105 build_response(fet_res, query_url, recursion_counter, context).await
108 async fn build_response(
109 fetch_response: SearchAcceptedObjects,
111 recursion_counter: &mut i32,
112 context: &LemmyContext,
113 ) -> Result<SearchResponse, LemmyError> {
114 let domain = query_url.domain().context("url has no domain")?;
115 let mut response = SearchResponse {
116 type_: SearchType::All.to_string(),
123 match fetch_response {
124 SearchAcceptedObjects::Person(p) => {
125 let user_uri = p.inner.id(domain)?.context("person has no id")?;
127 let user = get_or_fetch_and_upsert_user(&user_uri, context, recursion_counter).await?;
129 response.users = vec![
130 blocking(context.pool(), move |conn| {
131 UserViewSafe::read(conn, user.id)
136 SearchAcceptedObjects::Group(g) => {
137 let community_uri = g.inner.id(domain)?.context("group has no id")?;
140 get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?;
142 response.communities = vec![
143 blocking(context.pool(), move |conn| {
144 CommunityView::read(conn, community.id, None)
149 SearchAcceptedObjects::Page(p) => {
150 let p = Post::from_apub(&p, context, query_url, recursion_counter).await?;
153 vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
155 SearchAcceptedObjects::Comment(c) => {
156 let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?;
158 response.comments = vec![
159 blocking(context.pool(), move |conn| {
160 CommentView::read(conn, c.id, None)
170 async fn delete_object_locally(query_url: &Url, context: &LemmyContext) -> Result<(), LemmyError> {
171 let res = find_object_by_id(context, query_url.to_owned()).await?;
173 Object::Comment(c) => {
174 blocking(context.pool(), move |conn| {
175 Comment::update_deleted(conn, c.id, true)
180 blocking(context.pool(), move |conn| {
181 Post::update_deleted(conn, p.id, true)
186 // TODO: implement update_deleted() for user, move it to ApubObject trait
187 blocking(context.pool(), move |conn| {
188 User_::delete_account(conn, u.id)
192 Object::Community(c) => {
193 blocking(context.pool(), move |conn| {
194 Community::update_deleted(conn, c.id, true)
198 Object::PrivateMessage(pm) => {
199 blocking(context.pool(), move |conn| {
200 PrivateMessage::update_deleted(conn, pm.id, true)
205 Err(anyhow!("Object was deleted").into())