Add script for updating source/_data/numbers.json

The JSON it produces is slightly different from what we had before:

1. indentation is a bit different;
2. it uses non-breaking space as thousands separator. I discussed this
   with @lostinlight and persuaded her that NBSP is less confusing than
   commas, because different cultures use different separators;
3. `lastUpdate` uses a format like "27 Jun 2023" rather than "27/06/23".
   Again, I discussed this with @lostinlight and she agreed that the new
   format is less confusing.

Fixes #152.
This commit is contained in:
Alexander Batischev 2023-06-27 00:21:28 +03:00
parent 75a45a99b7
commit 6c9725133c
No known key found for this signature in database
GPG Key ID: 356961A20C8BFD03
2 changed files with 206 additions and 137 deletions

View File

@ -1,139 +1,115 @@
{ {
"list": "list": {
{ "lastUpdate": "27 Jun 2023",
"lastUpdate": "05/01/23", "diaspora": {
"diaspora": "population": "640\u00a0909",
{ "activeUsers": "22\u00a0827",
"population": "638.079", "podsNum": "103"
"activeUsers": "33.328", },
"podsNum": "134" "friendica": {
}, "population": "20\u00a0553",
"friendica": "activeUsers": "5\u00a0285",
{ "podsNum": "366"
"population": "20.923", },
"activeUsers": "6.990", "hubzilla": {
"podsNum": "423" "population": "5\u00a0163",
}, "activeUsers": "2\u00a0138",
"hubzilla": "podsNum": "78"
{ },
"population": "4.917", "mastodon": {
"activeUsers": "2.197", "population": "7\u00a0523\u00a0173",
"podsNum": "99" "activeUsers": "3\u00a0012\u00a0024",
}, "podsNum": "11\u00a0580"
"mastodon": },
{ "gnusocial": {
"population": "6.466.240", "population": "314",
"activeUsers": "4.213.494", "activeUsers": "266",
"podsNum": "12.573" "podsNum": "21"
}, },
"gnusocial": "pleroma": {
{ "population": "104\u00a0287",
"population": "2.786", "activeUsers": "34\u00a0135",
"activeUsers": "910", "podsNum": "1\u00a0184"
"podsNum": "24" },
}, "misskey": {
"pleroma": "population": "288\u00a0957",
{ "activeUsers": "27\u00a0181",
"population": "125.595", "podsNum": "747"
"activeUsers": "53.669", },
"podsNum": "1.661" "peertube": {
}, "population": "280\u00a0741",
"misskey": "activeUsers": "61\u00a0665",
{ "podsNum": "1\u00a0133"
"population": "37.056", },
"activeUsers": "19.771", "pixelfed": {
"podsNum": "500" "population": "154\u00a0117",
}, "activeUsers": "50\u00a0221",
"peertube": "podsNum": "447"
{ },
"population": "309.723", "funkwhale": {
"activeUsers": "90.525", "population": "8\u00a0547",
"podsNum": "1.194" "activeUsers": "1\u00a0697",
}, "podsNum": "118"
"pixelfed": },
{ "writefreely": {
"population": "124.005", "population": "84\u00a0940",
"activeUsers": "53.107", "activeUsers": "15\u00a0065",
"podsNum": "450" "podsNum": "548"
}, },
"funkwhale": "plume": {
{ "population": "23\u00a0810",
"population": "5.253", "activeUsers": "1",
"activeUsers": "2.112", "podsNum": "47"
"podsNum": "139" },
}, "mobilizon": {
"writefreely": "population": "17\u00a0141",
{ "activeUsers": "17",
"population": "61.058", "podsNum": "112"
"activeUsers": "8.151", },
"podsNum": "567" "lemmy": {
}, "population": "2\u00a0502\u00a0391",
"plume": "activeUsers": "47\u00a0729",
{ "podsNum": "980"
"population": "23.557", },
"activeUsers": "", "gotosocial": {
"podsNum": "63" "population": "912",
}, "activeUsers": "38",
"mobilizon": "podsNum": "595"
{ },
"population": "12.960", "owncast": {
"activeUsers": "", "population": "206",
"podsNum": "116" "activeUsers": "206",
}, "podsNum": "206"
"lemmy": },
{ "bookwyrm": {
"population": "39.610", "population": "15\u00a0126",
"activeUsers": "3.922", "activeUsers": "8\u00a0161",
"podsNum": "87" "podsNum": "98"
}, },
"gotosocial": "microblogpub": {
{ "population": "80",
"population": "497", "activeUsers": "63",
"activeUsers": "", "podsNum": "80"
"podsNum": "497" },
}, "akkoma": {
"owncast": "population": "14\u00a0262",
{ "activeUsers": "13\u00a0789",
"population": "158", "podsNum": "498"
"activeUsers": "158", },
"podsNum": "186" "calckey": {
}, "population": "8\u00a0015",
"bookwyrm": "activeUsers": "6\u00a0217",
{ "podsNum": "339"
"population": "10.791", },
"activeUsers": "8.014", "hometown": {
"podsNum": "65" "population": "14\u00a0458",
}, "activeUsers": "13\u00a0043",
"microblogpub": "podsNum": "160"
{ },
"population": "84", "fediverse": {
"activeUsers": "53", "population": "11\u00a0708\u00a0102",
"podsNum": "100" "activeUsers": "3\u00a0321\u00a0768",
}, "podsNum": "19\u00a0440"
"akkoma":
{
"population": "9.425",
"activeUsers": "1.076",
"podsNum": "446"
},
"calckey":
{
"population": "2.700",
"activeUsers": "2.265",
"podsNum": "108"
},
"hometown":
{
"population": "10.304",
"activeUsers": "8.537",
"podsNum": "144"
},
"fediverse":
{
"population": "7.905.721",
"activeUsers": "4.508.331",
"podsNum": "19.576"
}
} }
} }
}

93
update-numbers.py Executable file
View File

@ -0,0 +1,93 @@
#!/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', 'calckey',
'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.')