#!/usr/bin/env python3 import datetime import json import requests SOFTWARES = ['diaspora', 'friendica', 'hubzilla', 'mastodon', 'gnusocial', 'pleroma', 'misskey', 'peertube', 'pixelfed', 'funkwhale', 'writefreely', 'plume', 'mobilizon', 'lemmy', 'gotosocial', 'owncast', 'bookwyrm', 'microblogpub', 'akkoma', 'firefish', 'hometown'] def number_to_string(number): """ Format number as string, using non-breaking space as a thousands separator. """ return f'{number:,}'.replace(',', '\u00a0') class SoftwareNumbers: """ Contains numbers for a given software, or for Fediverse overall. """ def __init__(self, softwarename): self.softwarename = softwarename self.population = 0 self.active_users = 0 self.pods_num = 0 def __add__(self, other): result = SoftwareNumbers(self.softwarename) result.population = self.population + other.population result.active_users = self.active_users + other.active_users result.pods_num = self.pods_num + other.pods_num return result def as_object(self): result = {} result[self.softwarename] = {} result[self.softwarename]['population'] = number_to_string(self.population) result[self.softwarename]['activeUsers'] = number_to_string(self.active_users) result[self.softwarename]['podsNum'] = number_to_string(self.pods_num) return result class StatsFetcher: """ Fetches Fediverse numbers and produces a report. """ def __init__(self): self.session = requests.Session() def __get_node_stats_for_software(self, softwarename): query = '{ nodes(status: "UP", softwarename: "' \ + softwarename \ + '") { total_users, active_users_halfyear } }' response = self.session.post('https://api.fediverse.observer/', data={'query': query}) return response.json().get('data', {}).get('nodes', []) def __calculate_stats_for_software(self, softwarename): node_stats = self.__get_node_stats_for_software(softwarename) result = SoftwareNumbers(softwarename) result.pods_num = len(node_stats) for node in node_stats: result.population += node.get('total_users', 0) result.active_users += node.get('active_users_halfyear', 0) return result def get_numbers(self): """ Obtain an object with numbers for each of the supported Fediverse platforms, and also a "fediverse" entry with totals. """ numbers = {} numbers['lastUpdate'] = datetime.date.today().strftime('%d %b %Y') fediverse = SoftwareNumbers('fediverse') for i, software in enumerate(SOFTWARES): print(f'({i+1:2}/{len(SOFTWARES):2}) Fetching stats for {software}...') stats = self.__calculate_stats_for_software(software) fediverse += stats numbers |= stats.as_object() numbers |= fediverse.as_object() return { 'list': numbers } if __name__ == '__main__': fetcher = StatsFetcher() numbers = fetcher.get_numbers() FILEPATH = 'source/_data/numbers.json' with open(FILEPATH, 'w') as f: f.write(json.dumps(numbers, indent=2)) print(f'{FILEPATH} updated.')