1 use crate::api::community::GetCommunityResponse;
2 use crate::api::post::GetPostsResponse;
3 use crate::apub::{get_apub_protocol_string, GroupExt};
4 use crate::db::community_view::CommunityView;
5 use crate::db::post_view::PostView;
6 use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
7 use crate::settings::Settings;
8 use activitystreams::collection::{OrderedCollection, UnorderedCollection};
9 use activitystreams::object::Page;
10 use activitystreams::BaseBox;
12 use isahc::prelude::*;
14 use serde::Deserialize;
16 fn fetch_node_info(domain: &str) -> Result<NodeInfo, Error> {
17 let well_known_uri = format!(
18 "{}://{}/.well-known/nodeinfo",
19 get_apub_protocol_string(),
22 let well_known = fetch_remote_object::<NodeInfoWellKnown>(&well_known_uri)?;
23 Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?)
26 fn fetch_communities_from_instance(domain: &str) -> Result<Vec<CommunityView>, Error> {
27 let node_info = fetch_node_info(domain)?;
29 if let Some(community_list_url) = node_info.metadata.community_list_url {
30 let collection = fetch_remote_object::<UnorderedCollection>(&community_list_url)?;
31 let object_boxes = collection
33 .get_many_items_base_boxes()
35 let communities: Result<Vec<CommunityView>, Error> = object_boxes
36 .map(|c| -> Result<CommunityView, Error> {
37 let group = c.to_owned().to_concrete::<GroupExt>()?;
38 CommunityView::from_group(&group, domain)
44 "{} is not a Lemmy instance, federation is not supported",
50 /// Returns a tuple of (username, domain) from an identifier like "main@dev.lemmy.ml"
51 fn split_identifier(identifier: &str) -> (String, String) {
52 let x: Vec<&str> = identifier.split('@').collect();
53 (x[0].replace("!", ""), x[1].to_string())
56 fn get_remote_community_uri(identifier: &str) -> String {
57 let (name, domain) = split_identifier(identifier);
58 format!("http://{}/federation/c/{}", domain, name)
61 pub fn fetch_remote_object<Response>(uri: &str) -> Result<Response, Error>
63 Response: for<'de> Deserialize<'de>,
65 if Settings::get().federation.tls_enabled && !uri.starts_with("https://") {
66 return Err(format_err!("Activitypub uri is insecure: {}", uri));
68 // TODO: should cache responses here when we are in production
69 // TODO: this function should return a future
70 let text = isahc::get(uri)?.text()?;
71 let res: Response = serde_json::from_str(&text)?;
75 pub fn get_remote_community_posts(identifier: &str) -> Result<GetPostsResponse, Error> {
76 let community = fetch_remote_object::<GroupExt>(&get_remote_community_uri(identifier))?;
77 let outbox_uri = &community.extension.get_outbox().to_string();
78 let outbox = fetch_remote_object::<OrderedCollection>(outbox_uri)?;
79 let items = outbox.collection_props.get_many_items_base_boxes();
81 let posts: Result<Vec<PostView>, Error> = items
83 .map(|obox: &BaseBox| {
84 let page = obox.clone().to_concrete::<Page>().unwrap();
85 PostView::from_page(&page)
88 Ok(GetPostsResponse { posts: posts? })
91 pub fn get_remote_community(identifier: &str) -> Result<GetCommunityResponse, failure::Error> {
92 let group = fetch_remote_object::<GroupExt>(&get_remote_community_uri(identifier))?;
93 // TODO: this is only for testing until we can call that function from GetPosts
94 // (once string ids are supported)
95 //dbg!(get_remote_community_posts(identifier)?);
97 let (_, domain) = split_identifier(identifier);
98 Ok(GetCommunityResponse {
101 community: CommunityView::from_group(&group, &domain)?,
106 pub fn get_following_instances() -> Vec<&'static str> {
114 pub fn get_all_communities() -> Result<Vec<CommunityView>, Error> {
115 let mut communities_list: Vec<CommunityView> = vec![];
116 for instance in &get_following_instances() {
117 match fetch_communities_from_instance(instance) {
118 Ok(mut c) => communities_list.append(c.as_mut()),
119 Err(e) => warn!("Failed to fetch instance list from remote instance: {}", e),
125 /// If community is on local instance, don't include the @instance part. This is only for displaying
126 /// to the user and should never be used otherwise.
127 pub fn format_community_name(name: &str, instance: &str) -> String {
128 if instance == Settings::get().hostname {
131 format!("!{}@{}", name, instance)