2021-01-12 16:12:41 +00:00
|
|
|
use crate::{
|
|
|
|
fetcher::{
|
|
|
|
fetch::fetch_remote_object,
|
|
|
|
get_or_fetch_and_upsert_community,
|
2021-03-10 22:33:55 +00:00
|
|
|
get_or_fetch_and_upsert_person,
|
2021-01-12 16:12:41 +00:00
|
|
|
is_deleted,
|
|
|
|
},
|
|
|
|
find_object_by_id,
|
|
|
|
objects::FromApub,
|
|
|
|
GroupExt,
|
|
|
|
NoteExt,
|
|
|
|
Object,
|
|
|
|
PageExt,
|
|
|
|
PersonExt,
|
|
|
|
};
|
|
|
|
use activitystreams::base::BaseExt;
|
|
|
|
use anyhow::{anyhow, Context};
|
2021-03-01 13:08:41 +00:00
|
|
|
use lemmy_api_structs::{blocking, site::SearchResponse};
|
2021-01-12 16:12:41 +00:00
|
|
|
use lemmy_db_queries::{
|
|
|
|
source::{
|
|
|
|
comment::Comment_,
|
|
|
|
community::Community_,
|
|
|
|
post::Post_,
|
|
|
|
private_message::PrivateMessage_,
|
2021-03-10 22:33:55 +00:00
|
|
|
person::Person_,
|
2021-01-12 16:12:41 +00:00
|
|
|
},
|
|
|
|
SearchType,
|
|
|
|
};
|
|
|
|
use lemmy_db_schema::source::{
|
|
|
|
comment::Comment,
|
|
|
|
community::Community,
|
|
|
|
post::Post,
|
|
|
|
private_message::PrivateMessage,
|
2021-03-10 22:33:55 +00:00
|
|
|
person::Person,
|
2021-01-12 16:12:41 +00:00
|
|
|
};
|
|
|
|
use lemmy_db_views::{comment_view::CommentView, post_view::PostView};
|
2021-03-10 22:33:55 +00:00
|
|
|
use lemmy_db_views_actor::{community_view::CommunityView, person_view::PersonViewSafe};
|
2021-03-01 17:24:11 +00:00
|
|
|
use lemmy_utils::{settings::structs::Settings, LemmyError};
|
2021-01-12 16:12:41 +00:00
|
|
|
use lemmy_websocket::LemmyContext;
|
|
|
|
use log::debug;
|
|
|
|
use url::Url;
|
|
|
|
|
|
|
|
/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
|
|
|
|
#[derive(serde::Deserialize, Debug)]
|
2021-02-18 15:16:18 +00:00
|
|
|
#[serde(untagged)]
|
2021-01-12 16:12:41 +00:00
|
|
|
enum SearchAcceptedObjects {
|
|
|
|
Person(Box<PersonExt>),
|
|
|
|
Group(Box<GroupExt>),
|
|
|
|
Page(Box<PageExt>),
|
|
|
|
Comment(Box<NoteExt>),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
|
|
|
|
///
|
|
|
|
/// Some working examples for use with the `docker/federation/` setup:
|
|
|
|
/// http://lemmy_alpha:8541/c/main, or !main@lemmy_alpha:8541
|
|
|
|
/// http://lemmy_beta:8551/u/lemmy_alpha, or @lemmy_beta@lemmy_beta:8551
|
|
|
|
/// http://lemmy_gamma:8561/post/3
|
|
|
|
/// http://lemmy_delta:8571/comment/2
|
|
|
|
pub async fn search_by_apub_id(
|
|
|
|
query: &str,
|
|
|
|
context: &LemmyContext,
|
|
|
|
) -> Result<SearchResponse, LemmyError> {
|
|
|
|
// Parse the shorthand query url
|
|
|
|
let query_url = if query.contains('@') {
|
|
|
|
debug!("Search for {}", query);
|
|
|
|
let split = query.split('@').collect::<Vec<&str>>();
|
|
|
|
|
2021-03-10 22:33:55 +00:00
|
|
|
// Person type will look like ['', username, instance]
|
2021-01-12 16:12:41 +00:00
|
|
|
// Community will look like [!community, instance]
|
|
|
|
let (name, instance) = if split.len() == 3 {
|
|
|
|
(format!("/u/{}", split[1]), split[2])
|
|
|
|
} else if split.len() == 2 {
|
|
|
|
if split[0].contains('!') {
|
|
|
|
let split2 = split[0].split('!').collect::<Vec<&str>>();
|
|
|
|
(format!("/c/{}", split2[1]), split[1])
|
|
|
|
} else {
|
|
|
|
return Err(anyhow!("Invalid search query: {}", query).into());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return Err(anyhow!("Invalid search query: {}", query).into());
|
|
|
|
};
|
|
|
|
|
|
|
|
let url = format!(
|
|
|
|
"{}://{}{}",
|
|
|
|
Settings::get().get_protocol_string(),
|
|
|
|
instance,
|
|
|
|
name
|
|
|
|
);
|
|
|
|
Url::parse(&url)?
|
|
|
|
} else {
|
|
|
|
Url::parse(&query)?
|
|
|
|
};
|
|
|
|
|
|
|
|
let recursion_counter = &mut 0;
|
|
|
|
let fetch_response =
|
|
|
|
fetch_remote_object::<SearchAcceptedObjects>(context.client(), &query_url, recursion_counter)
|
|
|
|
.await;
|
|
|
|
if is_deleted(&fetch_response) {
|
|
|
|
delete_object_locally(&query_url, context).await?;
|
|
|
|
}
|
|
|
|
|
2021-01-15 04:56:53 +00:00
|
|
|
// Necessary because we get a stack overflow using FetchError
|
|
|
|
let fet_res = fetch_response.map_err(|e| LemmyError::from(e.inner))?;
|
|
|
|
build_response(fet_res, query_url, recursion_counter, context).await
|
2021-01-12 16:12:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn build_response(
|
|
|
|
fetch_response: SearchAcceptedObjects,
|
|
|
|
query_url: Url,
|
|
|
|
recursion_counter: &mut i32,
|
|
|
|
context: &LemmyContext,
|
|
|
|
) -> Result<SearchResponse, LemmyError> {
|
|
|
|
let domain = query_url.domain().context("url has no domain")?;
|
|
|
|
let mut response = SearchResponse {
|
|
|
|
type_: SearchType::All.to_string(),
|
|
|
|
comments: vec![],
|
|
|
|
posts: vec![],
|
|
|
|
communities: vec![],
|
|
|
|
users: vec![],
|
|
|
|
};
|
|
|
|
|
|
|
|
match fetch_response {
|
|
|
|
SearchAcceptedObjects::Person(p) => {
|
2021-03-10 22:33:55 +00:00
|
|
|
let person_uri = p.inner.id(domain)?.context("person has no id")?;
|
2021-01-12 16:12:41 +00:00
|
|
|
|
2021-03-10 22:33:55 +00:00
|
|
|
let person = get_or_fetch_and_upsert_person(&person_uri, context, recursion_counter).await?;
|
2021-01-12 16:12:41 +00:00
|
|
|
|
|
|
|
response.users = vec![
|
|
|
|
blocking(context.pool(), move |conn| {
|
2021-03-10 22:33:55 +00:00
|
|
|
PersonViewSafe::read(conn, person.id)
|
2021-01-12 16:12:41 +00:00
|
|
|
})
|
|
|
|
.await??,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
SearchAcceptedObjects::Group(g) => {
|
|
|
|
let community_uri = g.inner.id(domain)?.context("group has no id")?;
|
|
|
|
|
|
|
|
let community =
|
|
|
|
get_or_fetch_and_upsert_community(community_uri, context, recursion_counter).await?;
|
|
|
|
|
|
|
|
response.communities = vec![
|
|
|
|
blocking(context.pool(), move |conn| {
|
|
|
|
CommunityView::read(conn, community.id, None)
|
|
|
|
})
|
|
|
|
.await??,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
SearchAcceptedObjects::Page(p) => {
|
|
|
|
let p = Post::from_apub(&p, context, query_url, recursion_counter).await?;
|
|
|
|
|
|
|
|
response.posts =
|
|
|
|
vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
|
|
|
|
}
|
|
|
|
SearchAcceptedObjects::Comment(c) => {
|
|
|
|
let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?;
|
|
|
|
|
|
|
|
response.comments = vec![
|
|
|
|
blocking(context.pool(), move |conn| {
|
|
|
|
CommentView::read(conn, c.id, None)
|
|
|
|
})
|
|
|
|
.await??,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn delete_object_locally(query_url: &Url, context: &LemmyContext) -> Result<(), LemmyError> {
|
|
|
|
let res = find_object_by_id(context, query_url.to_owned()).await?;
|
|
|
|
match res {
|
|
|
|
Object::Comment(c) => {
|
|
|
|
blocking(context.pool(), move |conn| {
|
|
|
|
Comment::update_deleted(conn, c.id, true)
|
|
|
|
})
|
|
|
|
.await??;
|
|
|
|
}
|
|
|
|
Object::Post(p) => {
|
|
|
|
blocking(context.pool(), move |conn| {
|
|
|
|
Post::update_deleted(conn, p.id, true)
|
|
|
|
})
|
|
|
|
.await??;
|
|
|
|
}
|
2021-03-10 22:33:55 +00:00
|
|
|
Object::Person(u) => {
|
2021-01-12 16:12:41 +00:00
|
|
|
// TODO: implement update_deleted() for user, move it to ApubObject trait
|
|
|
|
blocking(context.pool(), move |conn| {
|
2021-03-10 22:33:55 +00:00
|
|
|
Person::delete_account(conn, u.id)
|
2021-01-12 16:12:41 +00:00
|
|
|
})
|
|
|
|
.await??;
|
|
|
|
}
|
|
|
|
Object::Community(c) => {
|
|
|
|
blocking(context.pool(), move |conn| {
|
|
|
|
Community::update_deleted(conn, c.id, true)
|
|
|
|
})
|
|
|
|
.await??;
|
|
|
|
}
|
|
|
|
Object::PrivateMessage(pm) => {
|
|
|
|
blocking(context.pool(), move |conn| {
|
|
|
|
PrivateMessage::update_deleted(conn, pm.id, true)
|
|
|
|
})
|
|
|
|
.await??;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(anyhow!("Object was deleted").into())
|
|
|
|
}
|