from signedjson.key import read_signing_keys from signedjson.sign import sign_json import json import requests from systemd import journal from get_config import read_vars import datetime # Importing configuration from environment variables config = read_vars() # Getting signing key with open(config["server_key_file"]) as f: skey = read_signing_keys(f)[0] # Defining additional functions def current_time(): now = datetime.datetime.now() return int(now.timestamp() * 1000) def parse_roomid(roomid): return roomid.split(":")[1] def serveraddr(servername): x = requests.get("https://%s/.well-known/matrix/server" % servername) if x.status_code == 200: j = json.loads(x.text) return j["m.server"] else: journal.send(f"[WARNING] Got {x.status_code} discovering server address of {servername}") return False # Function for getting users' access tokens def get_access_token(userid): time = current_time() + config["auth_token_term"] * 1000 x = requests.post( f"https://{config['origin_server']}/_synapse/admin/v1/users/{userid}/login", json={"valid_until_ms": time}, headers={ "Authorization": "Bearer %s" % config["admin_auth_token"], "Content-Type": "application/json", }, ).text if "You are not a server admin" in x: journal.send("ERROR! The given token doesn't belogn to the server admin!") exit() y = json.loads(x) return y["access_token"] # Sign Matrix federation API requests ## From Matrix protocol documentation def authorization_headers( destination_name, request_method, request_target, content=None ): origin_name = config["origin_server_name"] request_json = { "method": request_method, "uri": request_target, "origin": origin_name, "destination": destination_name, } if content is not None: request_json["content"] = content signed_json = sign_json(request_json, origin_name, skey) for key, sig in signed_json["signatures"][origin_name].items(): return 'X-Matrix origin="%s",destination="%s",key="%s",sig="%s"' % ( origin_name, destination_name, key, sig, ) # Request the last event def request_last_event(destination, roomid): servers = [destination, "matrix.org", "inex.rocks", "sibnsk.net", "kde.org"] for i in servers: journal.send(f"[info] Trying access event_to_timestamp via {i}") try: server = serveraddr(i) except requests.exceptions.ConnectionError: journal.send(f"[WARNING] {i}'s well-known's are unreachable") continue except: journal.send(f"[WARNING] Can't request {i}'s well-known's for unknown reason. Likely {i} is unreachable or ssl certs expired") continue if server == False: continue res = current_time() auth = authorization_headers( i, "GET", f"/_matrix/federation/v1/timestamp_to_event/{roomid}?dir=b&ts={res}", ) try: x = requests.get( f"https://{server}/_matrix/federation/v1/timestamp_to_event/{roomid}", params={"dir": "b", "ts": res}, headers={"Authorization": auth}, ).text except requests.exceptions.ConnectionError: journal.send(f"[WARNING] {server} is unreachable") continue except: journal.send(f"[WARNING] Couldn't request timestamp_to_event from {server} for unknown reason. Likely {server} is unreachable or ssl certs expired") continue if "M_UNRECOGNIZED" in x: journal.send(f"[WARNING] Trying request {server} timestamp_to_event for {roomid}: got M_UNRECOGNIZED error") continue elif "M_NOT_FOUND" in x: journal.send(f"[WARNING] Unable to get the last event from {roomid}. {i} doesn't know room {roomid}") journal.send(f"{i} says: '{x}'") continue elif "event_id" in x: j = json.loads(x) return (i, server, j["event_id"]) else: journal.send(f"[WARNING] Unable to get the last event from {roomid}. Unknown error from {server}: '{x}'") continue journal.send(f"ERROR! Unable to get the last event from {roomid}. All {servers} don't know room {roomid} or support timestamp_to_event") return False # Request states of specified room def get_states(destination, server, roomid, last_event_id): auth = authorization_headers( destination, "GET", "/_matrix/federation/v1/state/%s?event_id=%s" % (roomid, last_event_id) ) return requests.get( f"https://{server}/_matrix/federation/v1/state/{roomid}?event_id={last_event_id}", headers={"Authorization": auth}, ).text # Get users list from states def get_users(states): x = json.loads(states) # jq .pdus.[].type users = [] x = x["pdus"] for i in x: if i["type"] == "m.room.member": users.append(i["sender"]) return users def filter_users(users): x = [] for i in users: if f":{config["origin_server_name"]}" in i: x.append(i) return x # Make a user join a room def mkjoins(roomid, users, server1, server2): for i in users: token = get_access_token(i) x = requests.post( f"https://{config['origin_server']}/_matrix/client/v3/join/{roomid}", params={"via": server1, "via": server2}, headers={"Authorization": "Bearer %s" % token}, ).text if roomid in x: journal.send(f"[info] Joined {i} to {roomid}") journal.send(x) return True else: journal.send(f"[info] Failed to join {i} to {roomid}") journal.send(x) return False def revitalize(roomid, server): journal.send(f"Got new roomid {roomid}") fe = request_last_event(server, roomid) if fe == False: return False else: x = get_states(fe[0], fe[1], roomid, fe[2]) y = get_users(x) z = filter_users(y) journal.send(f"Trying to add {z} to {roomid}") if mkjoins(roomid, z, server, fe[0]): return True else: return False