I’m a newbie to ActivityPub so please be patient with me.

All intros into ActivityPub speak about how a user of a server A subscribes to a specific community from server B, and then server A will be informed about changes in that community.

But on lemmy it’s possible to look at the posts of all communities. For a single concrete community it would be relatively easy: server A gets the request to serve the top post of a community on server B, so A simple asks B for the posts.

But there is also the “posts from all communities” tab on the lemmy front page. This opens questions:

Does each lemmy instance has a full copy of all posts of all communities? If this is true: How are new Instances discovered? Is each Instance distributing all updates to all other Instances?

If each lemmy instance has only a partial dataset (this theory is backed by [1] “Only if a least one user on your instance subscribes to the remote community, will the community send updates to your instance.”) then how is the “all posts” view composed? is it in reality not “all” but only “all posts that at least one user of this instance is subscribed to”?

If this is the case: what happens if a bad actor subscribes to all communities of all servers? Is there a maximum number of subscriptions per user?

The source of those questions is, that I’m looking for a way to subscribe to all events of all lemmy instances, to be able to build statistics about upvotes, new posts, comments etc. There seems to be a similar API endpoint for mastadon [2] but nothing for lemmy?!

  • rust_alt@discuss.online
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    2 months ago

    It’s also possible to just pull all posts from an instance. The API is easy to understand.

    main.rs
    use serde::{Deserialize, Serialize};
    use std::collections::HashMap;
    
    fn main() {
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap();
    
        let to_page = Some(5);
        let posts = rt.block_on(get_posts_to(to_page)).unwrap();
    
        println!("-----------");
        println!(
            "All posts to page {} as JSON:",
            to_page.map(|v| v.to_string()).unwrap_or("infinity".into())
        );
        println!("-----------");
        println!("{}", serde_json::to_string(&posts).unwrap());
    }
    
    #[derive(Serialize, Deserialize, Debug, Clone)]
    struct PostData {
        id: usize,
        name: String,
    }
    
    #[derive(Serialize, Deserialize, Debug)]
    struct PageItem {
        post: PostData,
    }
    
    #[derive(Serialize, Deserialize, Debug)]
    struct PostPageResult {
        posts: Vec<PageItem>,
    }
    
    async fn get_page(index: usize) -> Result<HashMap<usize, PostData>, ()> {
        let result = reqwest::get(format!(
            "https://programming.dev/api/v3/post/list?dataType=Post&listingType=All&sort=New&page={}",
            index
        ))
        .await;
    
        if let Ok(r) = result {
            if let Ok(text) = r.text().await {
                if let Ok(data) = serde_json::from_str(&text) {
                    let data: PostPageResult = data;
    
                    let map =
                        data.posts
                            .iter()
                            .fold(HashMap::new(), |mut map, post| {
                                map.insert(post.post.id, post.post.clone());
    
                                map
                            });
    
                    if map.len() > 0 {
                        return Ok(map);
                    }
                } else {
                    println!("{:?}", serde_json::from_str::<PostPageResult>(&text));
                }
            }
        }
    
        Err(())
    }
    
    /// If page is not `None` then it stops after the page count. Otherwise it continues forever
    async fn get_posts_to(page: Option<usize>) -> Result<HashMap<usize, PostData>, ()> {
        let mut idx = 1;
        let mut map = HashMap::new();
    
        while let Ok(more_posts) = get_page(idx).await {
            println!("page: {}, {:#?}", idx, more_posts);
            map.extend(more_posts.into_iter());
            idx += 1;
    
            if let Some(page) = page {
                if idx > page {
                    break;
                }
            }
        }
    
        Ok(map)
    }
    
    
    Cargo.toml
    [package]
    name = "lemmyposts"
    version = "0.1.0"
    edition = "2021"
    
    [dependencies]
    reqwest = "0.12.7"
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0.128"
    tokio = { version = "1.4", features = ["rt"] }
    
    • 7EP6vuI@feddit.orgOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      2 months ago

      Thanks for the suggestion and the code snippets.

      i want to see how votes/comments accumulate over time on a post, therefore i would have to poll the “all” posts endpoint in a regular interval. but I would either see new posts with small number of comments/upvoted, or already upvoted post, or i would have to download all posts in a regular time interval which seems impossible to me.

      • rust_alt@discuss.online
        link
        fedilink
        arrow-up
        3
        ·
        edit-2
        2 months ago

        Comments are also easy, the API allows pulling them by latest too. If I was writing a search engine, I would probably just track all known instances and just pull local content from them instead of deduplicating. I haven’t really looked at how votes are federated though, so that might be more complicated to keep updated.

        I expect just syncing posts and comments from all instances to be mostly easy. In the past I was able to pull all posts and comments from smaller instances in like less than 10 minutes. It’s mostly just text so it doesn’t take that long. After it’s pulled, it can be kept mostly up to date by just pulling to the last date received, and should take much less time than the first sync.

        I’ve noticed there’s lots of stuff on Lemmy that fails to federate to other instances. I think there’s also actually a 3000€ reward at the moment for improving federation, so if you spend very much time on it, it might be a good idea to see if it can be claimed. Though, I don’t really know how the milestone system works, and it might only be available to inside contributors.