]> Untitled Git - lemmy.git/blob - server/src/apub/fetcher.rs
Adding back in post fetching.
[lemmy.git] / server / src / apub / fetcher.rs
1 use super::*;
2
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(),
8     domain
9   ))?;
10   let well_known = fetch_remote_object::<NodeInfoWellKnown>(&well_known_uri)?;
11   Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?)
12 }
13
14 /// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
15 /// timeouts etc.
16 pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
17 where
18   Response: for<'de> Deserialize<'de>,
19 {
20   if !is_apub_id_valid(&url) {
21     return Err(format_err!("Activitypub uri invalid or blocked: {}", url));
22   }
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)
28     .timeout(timeout)
29     .body(())?
30     .send()?
31     .text()?;
32   let res: Response = serde_json::from_str(&text)?;
33   Ok(res)
34 }
35
36 /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
37 #[serde(untagged)]
38 #[derive(serde::Deserialize, Debug)]
39 pub enum SearchAcceptedObjects {
40   Person(Box<PersonExt>),
41   Group(Box<GroupExt>),
42   Page(Box<Page>),
43 }
44
45 /// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
46 ///
47 /// Some working examples for use with the docker/federation/ setup:
48 /// http://lemmy_alpha:8540/c/main
49 /// http://lemmy_alpha:8540/u/lemmy_alpha
50 /// http://lemmy_alpha:8540/p/3
51 pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchResponse, Error> {
52   let query_url = Url::parse(&query)?;
53   let mut response = SearchResponse {
54     type_: SearchType::All.to_string(),
55     comments: vec![],
56     posts: vec![],
57     communities: vec![],
58     users: vec![],
59   };
60   match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
61     SearchAcceptedObjects::Person(p) => {
62       let user_uri = p.base.base.object_props.get_id().unwrap().to_string();
63       let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
64       response.users = vec![UserView::read(conn, user.id)?];
65     }
66     SearchAcceptedObjects::Group(g) => {
67       let community_uri = g.base.base.object_props.get_id().unwrap().to_string();
68       let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
69       // TODO Maybe at some point in the future, fetch all the history of a community
70       // fetch_community_outbox(&c, conn)?;
71       response.communities = vec![CommunityView::read(conn, community.id, None)?];
72     }
73     SearchAcceptedObjects::Page(p) => {
74       let p = upsert_post(&PostForm::from_apub(&p, conn)?, conn)?;
75       response.posts = vec![PostView::read(conn, p.id, None)?];
76     }
77   }
78   Ok(response)
79 }
80
81 /// 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.
82 pub fn get_or_fetch_and_upsert_remote_user(
83   apub_id: &str,
84   conn: &PgConnection,
85 ) -> Result<User_, Error> {
86   match User_::read_from_actor_id(&conn, &apub_id) {
87     Ok(u) => {
88       // If its older than a day, re-fetch it
89       // TODO the less than needs to be tested
90       if u
91         .last_refreshed_at
92         .lt(&(naive_now() - chrono::Duration::days(1)))
93       {
94         debug!("Fetching and updating from remote user: {}", apub_id);
95         let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
96         let mut uf = UserForm::from_apub(&person, &conn)?;
97         uf.last_refreshed_at = Some(naive_now());
98         Ok(User_::update(&conn, u.id, &uf)?)
99       } else {
100         Ok(u)
101       }
102     }
103     Err(NotFound {}) => {
104       debug!("Fetching and creating remote user: {}", apub_id);
105       let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
106       let uf = UserForm::from_apub(&person, &conn)?;
107       Ok(User_::create(conn, &uf)?)
108     }
109     Err(e) => Err(Error::from(e)),
110   }
111 }
112
113 /// 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.
114 pub fn get_or_fetch_and_upsert_remote_community(
115   apub_id: &str,
116   conn: &PgConnection,
117 ) -> Result<Community, Error> {
118   match Community::read_from_actor_id(&conn, &apub_id) {
119     Ok(c) => {
120       // If its older than a day, re-fetch it
121       // TODO the less than needs to be tested
122       if c
123         .last_refreshed_at
124         .lt(&(naive_now() - chrono::Duration::days(1)))
125       {
126         debug!("Fetching and updating from remote community: {}", apub_id);
127         let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
128         let mut cf = CommunityForm::from_apub(&group, conn)?;
129         cf.last_refreshed_at = Some(naive_now());
130         Ok(Community::update(&conn, c.id, &cf)?)
131       } else {
132         Ok(c)
133       }
134     }
135     Err(NotFound {}) => {
136       debug!("Fetching and creating remote community: {}", apub_id);
137       let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
138       let cf = CommunityForm::from_apub(&group, conn)?;
139       Ok(Community::create(conn, &cf)?)
140     }
141     Err(e) => Err(Error::from(e)),
142   }
143 }
144
145 fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
146   let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
147   match existing {
148     Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
149     Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
150     Err(e) => Err(Error::from(e)),
151   }
152 }
153
154 // TODO It should not be fetching data from a community outbox.
155 // All posts, comments, comment likes, etc should be posts to our community_inbox
156 // The only data we should be periodically fetching (if it hasn't been fetched in the last day
157 // maybe), is community and user actors
158 // and user actors
159 // Fetch all posts in the outbox of the given user, and insert them into the database.
160 // fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, Error> {
161 //   let outbox_url = Url::parse(&community.get_outbox_url())?;
162 //   let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
163 //   let items = outbox.collection_props.get_many_items_base_boxes();
164
165 //   Ok(
166 //     items
167 //       .unwrap()
168 //       .map(|obox: &BaseBox| -> Result<PostForm, Error> {
169 //         let page = obox.clone().to_concrete::<Page>()?;
170 //         PostForm::from_page(&page, conn)
171 //       })
172 //       .map(|pf| upsert_post(&pf?, conn))
173 //       .collect::<Result<Vec<Post>, Error>>()?,
174 //   )
175 // }