3 // Fetch nodeinfo metadata from a remote instance.
4 fn _fetch_node_info(domain: &str) -> Result<NodeInfo, Error> {
5 let well_known_uri = Url::parse(&format!(
6 "{}://{}/.well-known/nodeinfo",
7 get_apub_protocol_string(),
10 let well_known = fetch_remote_object::<NodeInfoWellKnown>(&well_known_uri)?;
11 Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?)
14 /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
16 pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
18 Response: for<'de> Deserialize<'de>,
20 if !is_apub_id_valid(&url) {
21 return Err(format_err!("Activitypub uri invalid or blocked: {}", url));
23 // TODO: this function should return a future
24 let timeout = Duration::from_secs(60);
25 let text = Request::get(url.as_str())
26 .header("Accept", APUB_JSON_CONTENT_TYPE)
27 .connect_timeout(timeout)
32 let res: Response = serde_json::from_str(&text)?;
36 /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
38 #[derive(serde::Deserialize, Debug)]
39 pub enum SearchAcceptedObjects {
40 Person(Box<PersonExt>),
44 /// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
46 /// Some working examples for use with the docker/federation/ setup:
47 /// http://lemmy_alpha:8540/c/main
48 /// http://lemmy_alpha:8540/u/lemmy_alpha
49 /// http://lemmy_alpha:8540/p/3
50 pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchResponse, Error> {
51 let query_url = Url::parse(&query)?;
52 let mut response = SearchResponse {
53 type_: SearchType::All.to_string(),
59 match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
60 SearchAcceptedObjects::Person(p) => {
61 let user_uri = p.base.base.object_props.get_id().unwrap().to_string();
62 let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
63 response.users = vec![UserView::read(conn, user.id)?];
65 SearchAcceptedObjects::Group(g) => {
66 let community_uri = g.base.base.object_props.get_id().unwrap().to_string();
67 let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
68 // TODO Maybe at some point in the future, fetch all the history of a community
69 // fetch_community_outbox(&c, conn)?;
70 response.communities = vec![CommunityView::read(conn, community.id, None)?];
76 /// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
77 pub fn get_or_fetch_and_upsert_remote_user(
80 ) -> Result<User_, Error> {
81 match User_::read_from_actor_id(&conn, &apub_id) {
83 // If its older than a day, re-fetch it
84 // TODO the less than needs to be tested
87 .lt(&(naive_now() - chrono::Duration::days(1)))
89 debug!("Fetching and updating from remote user: {}", apub_id);
90 let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
91 let mut uf = UserForm::from_apub(&person, &conn)?;
92 uf.last_refreshed_at = Some(naive_now());
93 Ok(User_::update(&conn, u.id, &uf)?)
99 debug!("Fetching and creating remote user: {}", apub_id);
100 let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
101 let uf = UserForm::from_apub(&person, &conn)?;
102 Ok(User_::create(conn, &uf)?)
104 Err(e) => Err(Error::from(e)),
108 /// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
109 pub fn get_or_fetch_and_upsert_remote_community(
112 ) -> Result<Community, Error> {
113 match Community::read_from_actor_id(&conn, &apub_id) {
115 // If its older than a day, re-fetch it
116 // TODO the less than needs to be tested
119 .lt(&(naive_now() - chrono::Duration::days(1)))
121 debug!("Fetching and updating from remote community: {}", apub_id);
122 let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
123 let mut cf = CommunityForm::from_apub(&group, conn)?;
124 cf.last_refreshed_at = Some(naive_now());
125 Ok(Community::update(&conn, c.id, &cf)?)
130 Err(NotFound {}) => {
131 debug!("Fetching and creating remote community: {}", apub_id);
132 let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
133 let cf = CommunityForm::from_apub(&group, conn)?;
134 Ok(Community::create(conn, &cf)?)
136 Err(e) => Err(Error::from(e)),
140 // TODO Maybe add post, comment searching / caching at a later time
141 // fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
142 // let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
144 // Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
145 // Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
146 // Err(e) => Err(Error::from(e)),
150 // TODO It should not be fetching data from a community outbox.
151 // All posts, comments, comment likes, etc should be posts to our community_inbox
152 // The only data we should be periodically fetching (if it hasn't been fetched in the last day
153 // maybe), is community and user actors
155 // Fetch all posts in the outbox of the given user, and insert them into the database.
156 // fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, Error> {
157 // let outbox_url = Url::parse(&community.get_outbox_url())?;
158 // let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
159 // let items = outbox.collection_props.get_many_items_base_boxes();
164 // .map(|obox: &BaseBox| -> Result<PostForm, Error> {
165 // let page = obox.clone().to_concrete::<Page>()?;
166 // PostForm::from_page(&page, conn)
168 // .map(|pf| upsert_post(&pf?, conn))
169 // .collect::<Result<Vec<Post>, Error>>()?,