fediverse/fediverse.py

728 líneas
20 KiB
Python
Original Vista normal Històric

2020-05-17 21:28:03 +02:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
2020-05-17 21:40:19 +02:00
import time
2020-05-17 21:28:03 +02:00
start_time = time.time()
import urllib3
from urllib3 import exceptions
from datetime import datetime
from subprocess import call
from mastodon import Mastodon
import threading
import os
import json
import signal
import sys
import os.path
import requests
from requests import exceptions
import operator
import calendar
import psycopg2
from itertools import product
from multiprocessing import Pool, Lock, Process, Queue, current_process, Manager
import multiprocessing
import aiohttp
import aiodns
import asyncio
from aiohttp import ClientError, ClientSession, ClientConnectionError, ClientConnectorError, ClientSSLError, ClientConnectorSSLError, ServerTimeoutError
from asyncio import TimeoutError
import socket
from socket import gaierror, gethostbyname
2020-05-17 21:28:03 +02:00
from decimal import *
getcontext().prec = 2
apis = ['/nodeinfo/2.0?', '/nodeinfo/2.0.json?', '/main/nodeinfo/2.0?', '/api/statusnet/config?', '/api/nodeinfo/2.0.json?', '/api/nodeinfo?', '/api/v1/instance?']
client_exceptions = (
aiohttp.ClientResponseError,
aiohttp.ClientConnectionError,
aiohttp.ClientConnectorError,
aiohttp.ClientError,
asyncio.TimeoutError,
socket.gaierror,
)
now = datetime.now()
2020-05-17 21:28:03 +02:00
###############################################################################
# INITIALISATION
###############################################################################
def is_json(myjson):
try:
json_object = json.loads(myjson)
except ValueError as e:
return False
return True
2020-05-17 21:28:03 +02:00
def alive_server(server, x):
2020-05-17 21:28:03 +02:00
server = server[0].rstrip('.').lower()
2020-05-17 21:28:03 +02:00
conn = None
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
2020-05-17 21:28:03 +02:00
cur.execute("select alive, software, users_api from fediverse where server=(%s)", (server,))
2020-05-17 21:28:03 +02:00
row = cur.fetchone()
2020-05-17 21:28:03 +02:00
if row != None:
was_alive = row[0]
serv_soft = row[1]
serv_api = row[2]
cur.close()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
alive = False
2020-05-17 21:28:03 +02:00
try:
data = requests.get('https://' + server + serv_api, timeout=3)
2020-05-17 21:28:03 +02:00
if serv_soft == "mastodon":
if serv_api == '/nodeinfo/2.0?':
try:
users = data.json()['usage']['users']['total']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
if serv_api == '/nodeinfo/2.0.json?':
try:
users = data.json()['usage']['users']['total']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
elif serv_api == '/api/v1/instance?':
try:
users = data.json()['stats']['user_count']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
if serv_soft == "pleroma" or serv_soft == "diaspora" or serv_soft == "peertube" or serv_soft == "pixelfed" or serv_soft == "hubzilla" or serv_soft == "writefreely" or serv_soft == "friendica":
try:
users = data.json()['usage']['users']['total']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
if serv_soft == "gnusocialv2" or serv_soft == "gnusocial":
try:
users = data.json()['usage']['users']['total']
if users == 0:
users = data.json()['usage']['users']['activeHalfyear']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
if serv_soft == "plume" or serv_soft == 'red' or serv_soft == "misskey" or serv_soft == "zap" or serv_soft == "prismo" or serv_soft == "ravenvale" or serv_soft == "osada" or serv_soft == "groundpolis":
try:
users = data.json()['usage']['users']['total']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
if serv_soft == "ganggo" or serv_soft == "squs" or serv_soft == "dolphin":
try:
users = data.json()['usage']['users']['total']
2020-06-04 19:38:53 +02:00
alive = True
except:
users = 0
2020-05-17 21:28:03 +02:00
print("Server " + str(server) + " (" + serv_soft + ") is alive!")
2020-05-17 21:28:03 +02:00
insert_sql = "INSERT INTO fediverse(server, users, updated_at, software, alive, users_api) VALUES(%s,%s,%s,%s,%s,%s) ON CONFLICT DO NOTHING"
conn = None
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
2020-05-17 21:28:03 +02:00
cur.execute(insert_sql, (server, users, now, serv_soft, alive, serv_api))
2020-05-17 21:28:03 +02:00
cur.execute("UPDATE fediverse SET users=(%s), updated_at=(%s), software=(%s), alive=(%s), users_api=(%s) where server=(%s)", (users, now, serv_soft, alive, serv_api, server))
2020-05-17 21:28:03 +02:00
cur.execute("UPDATE world SET checked='t' where server=(%s)", (server,))
2020-05-17 21:28:03 +02:00
conn.commit()
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
print(error)
2020-05-17 21:28:03 +02:00
finally:
2020-05-17 21:28:03 +02:00
if conn is not None:
conn.close()
2020-05-17 21:28:03 +02:00
except urllib3.exceptions.ProtocolError as protoerr:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except requests.exceptions.ChunkedEncodingError as chunkerr:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except KeyError as e:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except ValueError as verr:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except requests.exceptions.SSLError as errssl:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except requests.exceptions.HTTPError as errh:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except requests.exceptions.ConnectionError as errc:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except requests.exceptions.Timeout as errt:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
except requests.exceptions.RequestException as err:
2020-05-17 21:28:03 +02:00
print("Server " + server + " is dead :-(")
alive = False
pass
2020-05-17 21:28:03 +02:00
if alive == False:
2020-05-17 21:28:03 +02:00
conn = None
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
conn = psycopg2.connect(database=fediverse_db, user=fediverse_db_user, password="",
host="/var/run/postgresql", port="5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
cur.execute("UPDATE fediverse SET updated_at=(%s), alive=(%s) where server=(%s)", (now, alive, server))
cur.execute("UPDATE world SET checked='t' where server=(%s)", (server,))
conn.commit()
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
print(error)
2020-05-17 21:28:03 +02:00
finally:
2020-05-17 21:28:03 +02:00
if conn is not None:
conn.close()
2020-05-17 21:28:03 +02:00
def write_api(server, software, users, alive, api):
2020-05-17 21:28:03 +02:00
insert_sql = "INSERT INTO fediverse(server, updated_at, software, users, alive, users_api) VALUES(%s,%s,%s,%s,%s,%s) ON CONFLICT DO NOTHING"
conn = None
try:
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
cur.execute(insert_sql, (server, now, software, users, alive, api))
cur.execute("UPDATE fediverse SET updated_at=(%s), software=(%s), users=(%s), alive=(%s), users_api=(%s) where server=(%s)", (now, software, users, alive, api, server))
cur.execute("UPDATE world SET checked='t' where server=(%s)", (server,))
2020-05-17 21:28:03 +02:00
conn.commit()
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
2020-06-04 19:38:53 +02:00
print(error)
2020-05-17 21:28:03 +02:00
finally:
2020-05-17 21:28:03 +02:00
if conn is not None:
2020-06-04 19:38:53 +02:00
2020-05-17 21:28:03 +02:00
conn.close()
async def getsoft(server):
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
socket.gethostbyname(server)
2020-05-17 21:28:03 +02:00
except socket.gaierror:
2020-05-17 21:28:03 +02:00
return
2020-05-17 21:28:03 +02:00
soft = ''
url = 'https://' + server
timeout = aiohttp.ClientTimeout(total=3)
async with aiohttp.ClientSession(timeout=timeout) as session:
for api in apis:
try:
async with session.get(url+api) as response:
if response.status == 200:
try:
response_json = await response.json()
except:
pass
except aiohttp.ClientConnectorError as err:
pass
else:
if response.status == 200 and api != '/api/v1/instance?':
try:
soft = response_json['software']['name']
soft = soft.lower()
users = response_json['usage']['users']['total']
alive = True
write_api(server, soft, users, alive, api)
print("Server " + server + " is alive!")
return
except:
pass
if response.status == 200 and soft == '' and api == "/api/v1/instance?":
soft = 'mastodon'
users = response_json['stats']['user_count']
alive = True
write_api(server, soft, users, alive, api)
print("Server " + server + " is alive!")
2020-05-17 21:28:03 +02:00
def getserver(server, x):
2020-05-17 21:28:03 +02:00
server = server[0].rstrip('.').lower()
2020-05-17 21:28:03 +02:00
if server.find(".") == -1:
return
if server.find("@") != -1:
return
if server.find("/") != -1:
return
if server.find(":") != -1:
return
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
loop = asyncio.get_event_loop()
coroutines = [getsoft(server)]
soft = loop.run_until_complete(asyncio.gather(*coroutines, return_exceptions=True))
except:
2020-05-17 21:28:03 +02:00
pass
# Returns the parameter from the specified file
def get_parameter( parameter, file_path ):
# Check if secrets file exists
if not os.path.isfile(file_path):
print("File %s not found, exiting."%file_path)
sys.exit(0)
# Find parameter in file
with open( file_path ) as f:
for line in f:
if line.startswith( parameter ):
return line.replace(parameter + ":", "").strip()
# Cannot find parameter, exit
print(file_path + " Missing parameter %s "%parameter)
sys.exit(0)
# Load secrets from secrets file
secrets_filepath = "secrets/secrets.txt"
uc_client_id = get_parameter("uc_client_id", secrets_filepath)
uc_client_secret = get_parameter("uc_client_secret", secrets_filepath)
uc_access_token = get_parameter("uc_access_token", secrets_filepath)
# Load configuration from config file
2020-05-21 19:17:11 +02:00
config_filepath = "config/config.txt"
2020-05-19 23:54:55 +02:00
mastodon_hostname = get_parameter("mastodon_hostname", config_filepath)
2020-05-17 21:28:03 +02:00
# Load database config from db_config file
db_config_filepath = "config/db_config.txt"
fediverse_db = get_parameter("fediverse_db", db_config_filepath)
fediverse_db_user = get_parameter("fediverse_db_user", db_config_filepath)
# Initialise Mastodon API
mastodon = Mastodon(
client_id = uc_client_id,
client_secret = uc_client_secret,
access_token = uc_access_token,
api_base_url = 'https://' + mastodon_hostname,
)
# Initialise access headers
headers={ 'Authorization': 'Bearer %s'%uc_access_token }
###############################################################################
# main
if __name__ == '__main__':
2020-05-17 21:28:03 +02:00
total_servers = 0
total_users = 0
2020-05-17 21:28:03 +02:00
############################################################################
# set all world servers's checked column to False
2020-05-17 21:28:03 +02:00
try:
conn = None
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
cur.execute("UPDATE world SET checked='f'")
conn.commit()
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
2020-05-17 21:28:03 +02:00
print(error)
finally:
if conn is not None:
conn.close()
############################################################################
# get last check servers from fediverse DB
alive_servers = []
try:
conn = None
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
### get world servers list
cur.execute("select server from world where server in (select server from fediverse where users_api != '')")
alive_servers = []
for row in cur:
alive_servers.append(row[0])
cur.close()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
###########################################################################
# multiprocessing!
m = Manager()
q = m.Queue()
z = zip(alive_servers)
serv_number = len(alive_servers)
pool_tuple = [(x, q) for x in z]
with Pool(processes=64) as pool:
pool.starmap(alive_server, pool_tuple)
###########################################################################
print("Getting the remaining servers from world")
2020-05-17 21:28:03 +02:00
world_servers = []
try:
2020-05-17 21:28:03 +02:00
conn = None
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
2020-05-17 21:28:03 +02:00
### get world servers list
2020-05-17 21:28:03 +02:00
cur.execute("select server from world where checked='f'")
2020-05-17 21:28:03 +02:00
for row in cur:
2020-05-17 21:28:03 +02:00
world_servers.append(row[0])
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
###########################################################################
# multiprocessing!
m = Manager()
q = m.Queue()
z = zip(world_servers)
serv_number = len(world_servers)
pool_tuple = [(x, q) for x in z]
with Pool(processes=64) as pool:
pool.starmap(getserver, pool_tuple)
###########################################################################
# delete not alive servers from fediverse table
try:
conn = None
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
cur.execute("delete from fediverse where not alive")
conn.commit()
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
2020-05-17 21:28:03 +02:00
print(error)
2020-05-17 21:28:03 +02:00
finally:
2020-05-17 21:28:03 +02:00
if conn is not None:
2020-05-17 21:28:03 +02:00
conn.close()
2020-05-17 21:28:03 +02:00
###########################################################################
# get current total servers and users, get users from every software
2020-05-17 21:28:03 +02:00
2020-06-04 19:38:53 +02:00
gettotals_sql = "select count(server), sum(users) from fediverse where alive"
get_soft_totals_sql = "select software, sum(users) as users, count(server) as servers from fediverse where users != 0 and alive group by software order by users desc"
soft_total_project = []
soft_total_users = []
soft_total_servers = []
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
conn = None
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
2020-05-17 21:28:03 +02:00
cur.execute(gettotals_sql)
2020-05-17 21:28:03 +02:00
row = cur.fetchone()
2020-05-17 21:28:03 +02:00
total_servers = row[0]
total_users = row[1]
2020-05-17 21:28:03 +02:00
cur.execute(get_soft_totals_sql)
rows = cur.fetchall()
for row in rows:
soft_total_project.append(row[0])
soft_total_users.append(row[1])
soft_total_servers.append(row[2])
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
2020-05-17 21:28:03 +02:00
print(error)
2020-05-17 21:28:03 +02:00
finally:
2020-05-17 21:28:03 +02:00
if conn is not None:
2020-05-17 21:28:03 +02:00
conn.close()
2020-05-17 21:28:03 +02:00
###########################################################################
# get last check values and write current total ones
2020-05-17 21:28:03 +02:00
select_sql = "select total_servers, total_users from totals order by datetime desc limit 1"
insert_sql = "INSERT INTO totals(datetime, total_servers, total_users) VALUES(%s,%s,%s)"
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
conn = None
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
2020-05-17 21:28:03 +02:00
cur.execute(select_sql)
2020-05-17 21:28:03 +02:00
row = cur.fetchone()
2020-05-17 21:28:03 +02:00
if row != None:
2020-05-17 21:28:03 +02:00
servers_before = row[0]
users_before = row[1]
2020-05-17 21:28:03 +02:00
else:
servers_before = 0
users_before = 0
cur.execute(insert_sql, (now, total_servers, total_users))
conn.commit()
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
evo_servers = total_servers - servers_before
evo_users = total_users - users_before
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
2020-05-17 21:28:03 +02:00
print(error)
2020-05-17 21:28:03 +02:00
finally:
if conn is not None:
conn.close()
2020-05-17 21:28:03 +02:00
################################################################################
# write evo values
2020-05-17 21:28:03 +02:00
insert_sql = "INSERT INTO evo(datetime, servers, users) VALUES(%s,%s,%s)"
2020-05-17 21:28:03 +02:00
conn = None
2020-05-17 21:28:03 +02:00
try:
2020-05-17 21:28:03 +02:00
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
2020-05-17 21:28:03 +02:00
cur = conn.cursor()
2020-05-17 21:28:03 +02:00
cur.execute(insert_sql, (now, evo_servers, evo_users))
2020-05-17 21:28:03 +02:00
conn.commit()
2020-05-17 21:28:03 +02:00
cur.close()
2020-05-17 21:28:03 +02:00
except (Exception, psycopg2.DatabaseError) as error:
2020-05-17 21:28:03 +02:00
print(error)
2020-05-17 21:28:03 +02:00
finally:
2020-05-17 21:28:03 +02:00
if conn is not None:
2020-05-17 21:28:03 +02:00
conn.close()
2020-05-17 21:28:03 +02:00
##############################################################################
# get world's last update datetime
conn = None
try:
conn = psycopg2.connect(database = fediverse_db, user = fediverse_db_user, password = "", host = "/var/run/postgresql", port = "5432")
cur = conn.cursor()
cur.execute("select updated_at from world order by updated_at desc limit 1")
row = cur.fetchone()
last_update = row[0]
last_update = last_update.strftime('%m/%d/%Y, %H:%M:%S')
cur.close()
except (Exception, psycopg2.DatabaseError) as error:
print(error)
finally:
if conn is not None:
conn.close()
###############################################################################
# T O O T !
toot_text = "#fediverse alive servers stats" + " \n"
toot_text += "\n"
if evo_servers >= 0:
toot_text += "alive servers: " + str(total_servers) + " (+"+ str(evo_servers) + ") \n"
elif evo_servers < 0:
toot_text += "alive servers: " + str(total_servers) + " ("+ str(evo_servers) + ") \n"
if evo_users >= 0:
toot_text += "total users: " + str(total_users) + " (+"+ str(evo_users) + ") \n"
elif evo_users < 0:
toot_text += "total users: " + str(total_users) + " ("+ str(evo_users) + ") \n"
toot_text += "\n"
toot_text += "top five (soft: users servers):" + " \n"
toot_text += "\n"
toot_text += '{:>0}: {:>5} {:>5}'.format(str(soft_total_project[0]), str(soft_total_users[0]), str(soft_total_servers[0])) + " \n"
toot_text += '{:>0}: {:>11} {:>8}'.format(str(soft_total_project[1]), str(soft_total_users[1]), str(soft_total_servers[1])) + " \n"
toot_text += '{:>0}: {:>12} {:>7}'.format(str(soft_total_project[2]), str(soft_total_users[2]), str(soft_total_servers[2])) + " \n"
toot_text += '{:>0}: {:>13} {:>7}'.format(str(soft_total_project[3]), str(soft_total_users[3]), str(soft_total_servers[3])) + " \n"
toot_text += '{:>0}: {:>14} {:>7}'.format(str(soft_total_project[4]), str(soft_total_users[4]), str(soft_total_servers[4])) + " \n"
toot_text += "\n"
toot_text += "updated at " + str(last_update) + " \n"
print("Tooting...")
print(toot_text)
mastodon.status_post(toot_text, in_reply_to_id=None, )