+/// Updates the instance software and version
+///
+/// TODO: this should be async
+/// TODO: if instance has been dead for a long time, it should be checked less frequently
+fn update_instance_software(conn: &mut PgConnection, user_agent: &str) -> LemmyResult<()> {
+ info!("Updating instances software and versions...");
+
+ let client = Client::builder()
+ .user_agent(user_agent)
+ .timeout(REQWEST_TIMEOUT)
+ .build()?;
+
+ let instances = instance::table.get_results::<Instance>(conn)?;
+
+ for instance in instances {
+ let node_info_url = format!("https://{}/nodeinfo/2.0.json", instance.domain);
+
+ // The `updated` column is used to check if instances are alive. If it is more than three days
+ // in the past, no outgoing activities will be sent to that instance. However not every
+ // Fediverse instance has a valid Nodeinfo endpoint (its not required for Activitypub). That's
+ // why we always need to mark instances as updated if they are alive.
+ let default_form = InstanceForm::builder()
+ .domain(instance.domain.clone())
+ .updated(Some(naive_now()))
+ .build();
+ let form = match client.get(&node_info_url).send() {
+ Ok(res) if res.status().is_client_error() => {
+ // Instance doesnt have nodeinfo but sent a response, consider it alive
+ Some(default_form)
+ }
+ Ok(res) => match res.json::<NodeInfo>() {
+ Ok(node_info) => {
+ // Instance sent valid nodeinfo, write it to db
+ Some(
+ InstanceForm::builder()
+ .domain(instance.domain)
+ .updated(Some(naive_now()))
+ .software(node_info.software.and_then(|s| s.name))
+ .version(node_info.version.clone())
+ .build(),
+ )
+ }
+ Err(_) => {
+ // No valid nodeinfo but valid HTTP response, consider instance alive
+ Some(default_form)
+ }
+ },
+ Err(_) => {
+ // dead instance, do nothing
+ None
+ }
+ };
+ if let Some(form) = form {
+ diesel::update(instance::table.find(instance.id))
+ .set(form)
+ .execute(conn)?;
+ }
+ }
+ info!("Finished updating instances software and versions...");
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ #![allow(clippy::unwrap_used)]
+ #![allow(clippy::indexing_slicing)]
+
+ use lemmy_routes::nodeinfo::NodeInfo;
+ use reqwest::Client;
+
+ #[tokio::test]
+ #[ignore]
+ async fn test_nodeinfo() {
+ let client = Client::builder().build().unwrap();
+ let lemmy_ml_nodeinfo = client
+ .get("https://lemmy.ml/nodeinfo/2.0.json")
+ .send()
+ .await
+ .unwrap()
+ .json::<NodeInfo>()
+ .await
+ .unwrap();
+
+ assert_eq!(lemmy_ml_nodeinfo.software.unwrap().name.unwrap(), "lemmy");
+ }