2020-04-24 14:04:36 +00:00
use super ::* ;
2019-12-27 17:25:07 +00:00
2020-04-17 15:33:55 +00:00
// Fetch nodeinfo metadata from a remote instance.
2020-04-17 14:39:03 +00:00
fn _fetch_node_info ( domain : & str ) -> Result < NodeInfo , Error > {
2020-04-08 12:37:05 +00:00
let well_known_uri = Url ::parse ( & format! (
2020-03-18 21:09:00 +00:00
" {}://{}/.well-known/nodeinfo " ,
get_apub_protocol_string ( ) ,
2020-04-17 14:39:03 +00:00
domain
2020-04-08 12:37:05 +00:00
) ) ? ;
2020-03-18 21:09:00 +00:00
let well_known = fetch_remote_object ::< NodeInfoWellKnown > ( & well_known_uri ) ? ;
Ok ( fetch_remote_object ::< NodeInfo > ( & well_known . links . href ) ? )
2020-03-18 15:08:08 +00:00
}
2020-03-18 21:09:00 +00:00
2020-04-24 14:04:36 +00:00
// // TODO: move these to db
// // TODO use the last_refreshed_at
// fn upsert_community(
// community_form: &CommunityForm,
// conn: &PgConnection,
// ) -> Result<Community, Error> {
// let existing = Community::read_from_actor_id(conn, &community_form.actor_id);
// match existing {
// Err(NotFound {}) => Ok(Community::create(conn, &community_form)?),
// Ok(c) => Ok(Community::update(conn, c.id, &community_form)?),
// Err(e) => Err(Error::from(e)),
// }
// }
// fn upsert_user(user_form: &UserForm, conn: &PgConnection) -> Result<User_, Error> {
// let existing = User_::read_from_actor_id(conn, &user_form.actor_id);
// Ok(match existing {
// Err(NotFound {}) => User_::create(conn, &user_form)?,
// Ok(u) => User_::update(conn, u.id, &user_form)?,
// Err(e) => return Err(Error::from(e)),
// })
// }
2020-04-17 13:46:08 +00:00
fn upsert_post ( post_form : & PostForm , conn : & PgConnection ) -> Result < Post , Error > {
let existing = Post ::read_from_apub_id ( conn , & post_form . ap_id ) ;
match existing {
Err ( NotFound { } ) = > Ok ( Post ::create ( conn , & post_form ) ? ) ,
Ok ( p ) = > Ok ( Post ::update ( conn , p . id , & post_form ) ? ) ,
Err ( e ) = > Err ( Error ::from ( e ) ) ,
}
}
2020-04-17 15:33:55 +00:00
/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
/// timeouts etc.
/// TODO: add an optional param last_updated and only fetch if its too old
2020-04-08 12:37:05 +00:00
pub fn fetch_remote_object < Response > ( url : & Url ) -> Result < Response , Error >
2020-03-14 21:03:05 +00:00
where
Response : for < ' de > Deserialize < ' de > ,
{
2020-04-18 18:54:20 +00:00
if ! is_apub_id_valid ( & url ) {
2020-04-17 17:34:18 +00:00
return Err ( format_err! ( " Activitypub uri invalid or blocked: {} " , url ) ) ;
2020-03-18 21:09:00 +00:00
}
2020-03-14 21:03:05 +00:00
// TODO: this function should return a future
2020-04-07 15:29:23 +00:00
let timeout = Duration ::from_secs ( 60 ) ;
2020-04-08 12:37:05 +00:00
let text = Request ::get ( url . as_str ( ) )
2020-04-23 11:42:09 +00:00
. header ( " Accept " , APUB_JSON_CONTENT_TYPE )
2020-04-07 15:29:23 +00:00
. connect_timeout ( timeout )
. timeout ( timeout )
. body ( ( ) ) ?
. send ( ) ?
. text ( ) ? ;
2020-03-20 00:42:07 +00:00
let res : Response = serde_json ::from_str ( & text ) ? ;
Ok ( res )
2020-03-14 21:03:05 +00:00
}
2020-04-17 15:33:55 +00:00
/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
2020-04-17 13:46:08 +00:00
#[ serde(untagged) ]
2020-04-17 17:34:18 +00:00
#[ derive(serde::Deserialize, Debug) ]
2020-04-17 13:46:08 +00:00
pub enum SearchAcceptedObjects {
Person ( Box < PersonExt > ) ,
Group ( Box < GroupExt > ) ,
2020-04-24 14:04:36 +00:00
// Page(Box<Page>),
2020-04-17 13:46:08 +00:00
}
2020-04-17 15:33:55 +00:00
/// 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:8540/federation/c/main
/// http://lemmy_alpha:8540/federation/u/lemmy_alpha
/// http://lemmy_alpha:8540/federation/p/3
2020-04-17 13:46:08 +00:00
pub fn search_by_apub_id ( query : & str , conn : & PgConnection ) -> Result < SearchResponse , Error > {
let query_url = Url ::parse ( & query ) ? ;
let mut response = SearchResponse {
type_ : SearchType ::All . to_string ( ) ,
comments : vec ! [ ] ,
posts : vec ! [ ] ,
communities : vec ! [ ] ,
users : vec ! [ ] ,
} ;
match fetch_remote_object ::< SearchAcceptedObjects > ( & query_url ) ? {
SearchAcceptedObjects ::Person ( p ) = > {
2020-04-24 14:04:36 +00:00
let user = get_or_fetch_and_upsert_remote_user ( query , & conn ) ? ;
response . users = vec! [ UserView ::read ( conn , user . id ) ? ] ;
2020-04-17 13:46:08 +00:00
}
SearchAcceptedObjects ::Group ( g ) = > {
2020-04-24 14:04:36 +00:00
let community = get_or_fetch_and_upsert_remote_community ( query , & conn ) ? ;
// fetch_community_outbox(&c, conn)?;
response . communities = vec! [ CommunityView ::read ( conn , community . id , None ) ? ] ;
2020-04-17 13:46:08 +00:00
}
2020-04-24 14:04:36 +00:00
// SearchAcceptedObjects::Page(p) => {
// let p = upsert_post(&PostForm::from_page(&p, conn)?, conn)?;
// response.posts = vec![PostView::read(conn, p.id, None)?];
// }
2020-04-17 13:46:08 +00:00
}
Ok ( response )
}
2020-04-24 14:04:36 +00:00
// TODO It should not be fetching data from a community outbox.
// All posts, comments, comment likes, etc should be posts to our community_inbox
// The only data we should be periodically fetching (if it hasn't been fetched in the last day
// maybe), is community and user actors
// and user actors
2020-04-17 15:33:55 +00:00
/// Fetch all posts in the outbox of the given user, and insert them into the database.
2020-04-17 14:39:03 +00:00
fn fetch_community_outbox ( community : & Community , conn : & PgConnection ) -> Result < Vec < Post > , Error > {
2020-04-13 13:06:41 +00:00
let outbox_url = Url ::parse ( & community . get_outbox_url ( ) ) ? ;
2020-04-08 16:39:45 +00:00
let outbox = fetch_remote_object ::< OrderedCollection > ( & outbox_url ) ? ;
2020-04-03 05:02:43 +00:00
let items = outbox . collection_props . get_many_items_base_boxes ( ) ;
2020-03-14 21:03:05 +00:00
2020-04-08 12:08:33 +00:00
Ok (
items
. unwrap ( )
. map ( | obox : & BaseBox | -> Result < PostForm , Error > {
let page = obox . clone ( ) . to_concrete ::< Page > ( ) ? ;
PostForm ::from_page ( & page , conn )
} )
2020-04-17 13:46:08 +00:00
. map ( | pf | upsert_post ( & pf ? , conn ) )
2020-04-08 12:08:33 +00:00
. collect ::< Result < Vec < Post > , Error > > ( ) ? ,
)
2019-12-27 17:25:07 +00:00
}
2020-04-24 14:04:36 +00:00
/// 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.
pub fn get_or_fetch_and_upsert_remote_user ( apub_id : & str , conn : & PgConnection ) -> Result < User_ , Error > {
match User_ ::read_from_actor_id ( & conn , & apub_id ) {
Ok ( u ) = > {
// If its older than a day, re-fetch it
// TODO the less than needs to be tested
if u . last_refreshed_at . lt ( & ( naive_now ( ) - chrono ::Duration ::days ( 1 ) ) ) {
debug! ( " Fetching and updating from remote user: {} " , apub_id ) ;
let person = fetch_remote_object ::< PersonExt > ( & Url ::parse ( apub_id ) ? ) ? ;
let uf = UserForm ::from_person ( & person ) ? ;
uf . last_refreshed_at = Some ( naive_now ( ) ) ;
Ok ( User_ ::update ( & conn , u . id , & uf ) ? )
} else {
Ok ( u )
}
} ,
Err ( NotFound { } ) = > {
debug! ( " Fetching and creating remote user: {} " , apub_id ) ;
let person = fetch_remote_object ::< PersonExt > ( & Url ::parse ( apub_id ) ? ) ? ;
let uf = UserForm ::from_person ( & person ) ? ;
Ok ( User_ ::create ( conn , & uf ) ? )
}
Err ( e ) = > Err ( Error ::from ( e ) ) ,
}
2020-04-07 21:02:32 +00:00
}
2020-04-17 13:46:08 +00:00
2020-04-24 14:04:36 +00:00
/// 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.
pub fn get_or_fetch_and_upsert_remote_community ( apub_id : & str , conn : & PgConnection ) -> Result < Community , Error > {
match Community ::read_from_actor_id ( & conn , & apub_id ) {
Ok ( c ) = > {
// If its older than a day, re-fetch it
// TODO the less than needs to be tested
if c . last_refreshed_at . lt ( & ( naive_now ( ) - chrono ::Duration ::days ( 1 ) ) ) {
debug! ( " Fetching and updating from remote community: {} " , apub_id ) ;
let group = fetch_remote_object ::< GroupExt > ( & Url ::parse ( apub_id ) ? ) ? ;
let cf = CommunityForm ::from_group ( & group , conn ) ? ;
cf . last_refreshed_at = Some ( naive_now ( ) ) ;
Ok ( Community ::update ( & conn , c . id , & cf ) ? )
} else {
Ok ( c )
}
} ,
Err ( NotFound { } ) = > {
debug! ( " Fetching and creating remote community: {} " , apub_id ) ;
let group = fetch_remote_object ::< GroupExt > ( & Url ::parse ( apub_id ) ? ) ? ;
let cf = CommunityForm ::from_group ( & group , conn ) ? ;
Ok ( Community ::create ( conn , & cf ) ? )
}
Err ( e ) = > Err ( Error ::from ( e ) ) ,
}
2020-04-09 19:04:31 +00:00
}