summaryrefslogtreecommitdiffstats
path: root/server/src/apub/puller.rs
blob: 941fbc3fad3b5baa917f59ce013d8c7dc6021da9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
extern crate reqwest;

use crate::api::community::{GetCommunityResponse, ListCommunitiesResponse};
use crate::api::post::GetPostsResponse;
use crate::db::community_view::CommunityView;
use crate::settings::Settings;
use activitystreams::actor::apub::Group;
use activitystreams::collection::apub::{OrderedCollection, UnorderedCollection};
use failure::Error;

// TODO: right now all of the data is requested on demand, for production we will need to store
//       things in the local database to not ruin the performance

fn fetch_communities_from_instance(domain: &str) -> Result<Vec<CommunityView>, Error> {
  // TODO: check nodeinfo to make sure we are dealing with a lemmy instance
  //       -> means we need proper nodeinfo json classes instead of inline generation
  // TODO: follow pagination (seems like page count is missing?)
  // TODO: see if there is any standard for discovering remote actors, so we dont have to rely on lemmy apis
  let communities_uri = format!("http://{}/api/v1/communities/list?sort=Hot", domain);
  let communities1: ListCommunitiesResponse = reqwest::get(&communities_uri)?.json()?;
  let mut communities2 = communities1.communities;
  for c in &mut communities2 {
    c.name = format_community_name(&c.name, domain);
  }
  Ok(communities2)
}

// TODO: this should be cached or stored in the database
fn get_remote_community_uri(identifier: String) -> String {
  let x: Vec<&str> = identifier.split('@').collect();
  let name = x[0].replace("!", "");
  let instance = x[1];
  format!("http://{}/federation/c/{}", instance, name)
}

pub fn get_remote_community_posts(identifier: String) -> Result<GetPostsResponse, Error> {
  let community: Group = reqwest::get(&get_remote_community_uri(identifier))?.json()?;
  let outbox_uri = &community.ap_actor_props.get_outbox().to_string();
  let outbox: OrderedCollection = reqwest::get(outbox_uri)?.json()?;
  let items = outbox.collection_props.get_many_items_object_boxs();
  dbg!(items);
  unimplemented!()
}

pub fn get_remote_community(identifier: String) -> Result<GetCommunityResponse, failure::Error> {
  let community: Group = reqwest::get(&get_remote_community_uri(identifier.clone()))?.json()?;
  let followers_uri = &community
    .ap_actor_props
    .get_followers()
    .unwrap()
    .to_string();
  let outbox_uri = &community.ap_actor_props.get_outbox().to_string();
  let outbox: OrderedCollection = reqwest::get(outbox_uri)?.json()?;
  // TODO: this need to be done in get_remote_community_posts() (meaning we need to store the outbox uri?)
  let followers: UnorderedCollection = reqwest::get(followers_uri)?.json()?;

  // TODO: looks like a bunch of data is missing from the activitypub response
  // TODO: i dont think simple numeric ids are going to work, we probably need something like uuids
  Ok(GetCommunityResponse {
    moderators: vec![],
    admins: vec![],
    community: CommunityView {
      // TODO: we need to merge id and name into a single thing (stuff like @user@instance.com)
      id: 1337, //community.object_props.get_id()
      name: identifier,
      title: community
        .object_props
        .get_name_xsd_string()
        .unwrap()
        .to_string(),
      description: community
        .object_props
        .get_summary_xsd_string()
        .map(|s| s.to_string()),
      category_id: -1,
      creator_id: -1, //community.object_props.get_attributed_to_xsd_any_uri()
      removed: false,
      published: community
        .object_props
        .get_published()
        .unwrap()
        .as_ref()
        .naive_local()
        .to_owned(),
      updated: community
        .object_props
        .get_updated()
        .map(|u| u.as_ref().to_owned().naive_local()),
      deleted: false,
      nsfw: false,
      creator_name: "".to_string(),
      creator_avatar: None,
      category_name: "".to_string(),
      number_of_subscribers: *followers
        .collection_props
        .get_total_items()
        .unwrap()
        .as_ref() as i64, // TODO: need to use the same type
      number_of_posts: *outbox.collection_props.get_total_items().unwrap().as_ref() as i64,
      number_of_comments: -1,
      hot_rank: -1,
      user_id: None,
      subscribed: None,
    },
    online: 0,
  })
}

pub fn get_following_instances() -> Result<Vec<String>, Error> {
  let instance_list = match Settings::get().federated_instance.clone() {
    Some(f) => vec![f, Settings::get().hostname.clone()],
    None => vec![Settings::get().hostname.clone()],
  };
  Ok(instance_list)
}

pub fn get_all_communities() -> Result<Vec<CommunityView>, Error> {
  let mut communities_list: Vec<CommunityView> = vec![];
  for instance in &get_following_instances()? {
    communities_list.append(fetch_communities_from_instance(instance)?.as_mut());
  }
  Ok(communities_list)
}

/// If community is on local instance, don't include the @instance part
pub fn format_community_name(name: &str, instance: &str) -> String {
  if instance == Settings::get().hostname {
    format!("!{}", name)
  } else {
    format!("!{}@{}", name, instance)
  }
}