3 fetch::fetch_remote_object,
4 get_or_fetch_and_upsert_community,
5 get_or_fetch_and_upsert_person,
9 objects::{post::Page, FromApub},
15 use activitystreams::base::BaseExt;
16 use anyhow::{anyhow, Context};
17 use lemmy_api_common::{blocking, site::SearchResponse};
18 use lemmy_db_queries::{
21 community::Community_,
24 private_message::PrivateMessage_,
28 use lemmy_db_schema::source::{
33 private_message::PrivateMessage,
35 use lemmy_db_views::{comment_view::CommentView, post_view::PostView};
36 use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
37 use lemmy_utils::{settings::structs::Settings, LemmyError};
38 use lemmy_websocket::LemmyContext;
42 /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
43 #[derive(serde::Deserialize, Debug)]
45 enum SearchAcceptedObjects {
46 Person(Box<PersonExt>),
49 Comment(Box<NoteExt>),
52 /// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
54 /// Some working examples for use with the `docker/federation/` setup:
55 /// http://lemmy_alpha:8541/c/main, or !main@lemmy_alpha:8541
56 /// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551
57 /// http://lemmy_gamma:8561/post/3
58 /// http://lemmy_delta:8571/comment/2
59 pub async fn search_by_apub_id(
61 context: &LemmyContext,
62 ) -> Result<SearchResponse, LemmyError> {
63 // Parse the shorthand query url
64 let query_url = if query.contains('@') {
65 debug!("Search for {}", query);
66 let split = query.split('@').collect::<Vec<&str>>();
68 // Person type will look like ['', username, instance]
69 // Community will look like [!community, instance]
70 let (name, instance) = if split.len() == 3 {
71 (format!("/u/{}", split[1]), split[2])
72 } else if split.len() == 2 {
73 if split[0].contains('!') {
74 let split2 = split[0].split('!').collect::<Vec<&str>>();
75 (format!("/c/{}", split2[1]), split[1])
77 return Err(anyhow!("Invalid search query: {}", query).into());
80 return Err(anyhow!("Invalid search query: {}", query).into());
85 Settings::get().get_protocol_string(),
94 let recursion_counter = &mut 0;
96 fetch_remote_object::<SearchAcceptedObjects>(context.client(), &query_url, recursion_counter)
98 if is_deleted(&fetch_response) {
99 delete_object_locally(&query_url, context).await?;
102 // Necessary because we get a stack overflow using FetchError
103 let fet_res = fetch_response.map_err(|e| LemmyError::from(e.inner))?;
104 build_response(fet_res, query_url, recursion_counter, context).await
107 async fn build_response(
108 fetch_response: SearchAcceptedObjects,
110 recursion_counter: &mut i32,
111 context: &LemmyContext,
112 ) -> Result<SearchResponse, LemmyError> {
113 let domain = query_url.domain().context("url has no domain")?;
114 let mut response = SearchResponse {
115 type_: SearchType::All.to_string(),
122 match fetch_response {
123 SearchAcceptedObjects::Person(p) => {
124 let person_uri = p.inner.id(domain)?.context("person has no id")?;
126 let person = get_or_fetch_and_upsert_person(person_uri, context, recursion_counter).await?;
128 response.users = vec![
129 blocking(context.pool(), move |conn| {
130 PersonViewSafe::read(conn, person.id)
135 SearchAcceptedObjects::Group(g) => {
136 let community_uri = g.inner.id(domain)?.context("group has no id")?;
139 get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?;
141 response.communities = vec![
142 blocking(context.pool(), move |conn| {
143 CommunityView::read(conn, community.id, None)
148 SearchAcceptedObjects::Page(p) => {
149 let p = Post::from_apub(&p, context, query_url, recursion_counter, false).await?;
152 vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
154 SearchAcceptedObjects::Comment(c) => {
155 let c = Comment::from_apub(&c, context, query_url, recursion_counter, false).await?;
157 response.comments = vec![
158 blocking(context.pool(), move |conn| {
159 CommentView::read(conn, c.id, None)
169 async fn delete_object_locally(query_url: &Url, context: &LemmyContext) -> Result<(), LemmyError> {
170 let res = find_object_by_id(context, query_url.to_owned()).await?;
172 Object::Comment(c) => {
173 blocking(context.pool(), move |conn| {
174 Comment::update_deleted(conn, c.id, true)
179 blocking(context.pool(), move |conn| {
180 Post::update_deleted(conn, p.id, true)
184 Object::Person(u) => {
185 // TODO: implement update_deleted() for user, move it to ApubObject trait
186 blocking(context.pool(), move |conn| {
187 Person::delete_account(conn, u.id)
191 Object::Community(c) => {
192 blocking(context.pool(), move |conn| {
193 Community::update_deleted(conn, c.id, true)
197 Object::PrivateMessage(pm) => {
198 blocking(context.pool(), move |conn| {
199 PrivateMessage::update_deleted(conn, pm.id, true)
204 Err(anyhow!("Object was deleted").into())