xmpp-utils/bin/xmpp-doaptable

172 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python3
import json
import xml.etree.ElementTree
import requests
import colorama
import prettytable
CLIENTS_URL = 'https://raw.githubusercontent.com/xsf/xmpp.org/master/data/clients.json'
XMPP_DOAP_NS = 'https://linkmauve.fr/ns/xmpp-doap#' # the # is mandatory
# XPaths for <SupportedXep> in DOAPs
XPATH_DOAP_SUPPORTEDXEP = f'.//{{{XMPP_DOAP_NS}}}SupportedXep'
XPATH_DOAP_XEP = f'.//{{{XMPP_DOAP_NS}}}xep'
XPATH_DOAP_XEP_VERSION = f'.//{{{XMPP_DOAP_NS}}}version'
XPATH_DOAP_XEP_STATUS = f'.//{{{XMPP_DOAP_NS}}}status'
XPATH_RDF_RESOURCE = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}resource'
# XPaths for XEP definitions (at xmpp.org/extensions/)
XPATH_XEP_TITLE = './header/title'
XPATH_XEP_STATUS = './header/status'
SUPPORT_INDICATORS = {
'planned': colorama.Fore.RED + '' + colorama.Fore.RESET,
'partial': colorama.Fore.YELLOW + '' + colorama.Fore.RESET,
'complete': colorama.Fore.GREEN + '' + colorama.Fore.RESET,
'removed': colorama.Fore.RED + '🕇' + colorama.Fore.RESET,
'wontfix': colorama.Fore.RED + '' + colorama.Fore.RESET,
'SUPPORTED_UNKNOWN': colorama.Fore.YELLOW + '?' + colorama.Fore.RESET, # for XEPs mentioned in a <SupportedXep> tag but where the status isn't defined
'NOT_MENTIONED': colorama.Fore.RED + '' + colorama.Fore.RESET, # for XEPs that are not mentioned in clients' DOAP data
}
SUPPORT_LEGEND = {
'NOT_MENTIONED': 'No support',
'planned': 'Planned support',
'partial': 'Partial support',
'SUPPORTED_UNKNOWN': 'Supported, but status not specified',
'complete': 'Complete support',
'removed': 'Removed support',
'wontfix': "Client will never support this XEP",
}
XEP_LIST = [
30, # Service Discovery
45, # Multi-User Chat
166, # Jingle
167, # Jingle RTP Sessions
174, # Serverless Messaging
#176, # Jingle ICE-UDP Transport Method
#177, # Jingle Raw UDP Transport Method
198, # Stream Management
199, # XMPP Ping
258, # Security Labels in XMPP
280, # Message Carbons
352, # Client State Indication
357, # Push Notifications
363, # HTTP File Upload
368, # SRV records for XMPP-over-TLS
373, # OpenPGP for XMPP
384, # OMEMO encryption
449, # Stickers
454, # OMEMO media sharing
]
def acceptable_status(status):
if status >= 200 and status <= 400: # status ranges 200 (OK) and 300 (redirects) are okay
return True
return False
def xep_format_number(xep_number):
return f"{xep_number:04d}"
def xep_get_info(xep_number):
xep_number = xep_format_number(xep_number)
req = requests.get(f'https://xmpp.org/extensions/xep-{xep_number}.xml')
xep_tree = xml.etree.ElementTree.fromstring(req.text)
xep_title = xep_tree.find(XPATH_XEP_TITLE).text
xep_status = xep_tree.find(XPATH_XEP_STATUS).text
return {
'number': xep_number,
'title': xep_title,
'status': xep_status,
}
if __name__ == '__main__':
clients_req = requests.get(CLIENTS_URL)
if not acceptable_status(clients_req.status_code):
quit(f"Encountered error when loading client definitions from '{CLIENTS_URL}'!")
else:
colorama.init()
# print iconography legend
for key, info in SUPPORT_LEGEND.items():
print(SUPPORT_INDICATORS[key] + ': ' + info)
print("\rWorking…", end='')
table_data = {}
xeps_covered = set()
clients = json.loads(clients_req.text)
for client in clients:
if client['doap']:
doap_req = requests.get(client['doap'])
if not acceptable_status(doap_req.status_code):
print(f"\rEncountered error when loading client DOAP from '{client['doap']}'!", end='')
else:
doap = xml.etree.ElementTree.fromstring(doap_req.text)
client_data = {}
xep_entries = doap.findall(XPATH_DOAP_SUPPORTEDXEP)
for xep_entry in xep_entries:
xep_resource = xep_entry.find(XPATH_DOAP_XEP).attrib[XPATH_RDF_RESOURCE]
# the following abomination extracts the XEP number
# from an URL Like https://xmpp.org/extensions/xep-0030.html
xep_number = int(xep_resource.split('/')[-1].split('.')[0].split('-')[-1])
xep_status = xep_entry.find(XPATH_DOAP_XEP_STATUS)
if not xep_status is None:
xep_status = xep_status.text
xep_version = xep_entry.find(XPATH_DOAP_XEP_VERSION)
if not xep_version is None:
xep_version = xep_version.text
xeps_covered.add(xep_number)
client_data[xep_number] = {'status': xep_status, 'version': xep_version}
table_data[client['name']] = client_data
table = prettytable.PrettyTable()
field_names = ['XEP']
for name in table_data:
field_names.append(name)
table.field_names = field_names # doing append directly on PrettyTape.field_names majorly fucks shit up
table.align['XEP'] = 'l'
xep_list = XEP_LIST
if len(xep_list) == 0:
xep_list = sorted(xeps_covered)
for xep_number in xep_list:
xep_info = xep_get_info(xep_number)
row = [f"{xep_info['number']}: {xep_info['title']}({xep_info['status']})"]
for client_name, client_info in table_data.items():
if not xep_number in client_info:
row.append(SUPPORT_INDICATORS['NOT_MENTIONED'])
else:
if client_info[xep_number]['status'] is None:
row.append(SUPPORT_INDICATORS['SUPPORTED_UNKNOWN'])
else:
row.append(SUPPORT_INDICATORS[client_info[xep_number]['status']])
table.add_row(row)
print("\r", end='') # remove 'Working…'
print(table)