import os import subprocess import csv import time import requests import datetime import hardcoded_variables def delete_block_media(): # Take media_id from user media_id = input("\nEnter the media_id of the media you would like to delete and block on your server. (Example: For this media https://matrix.perthchat.org/_matrix/media/r0/download/matrix.org/eDmjusOjnHyFPOYGxlrOsULJ the media_id is 'eDmjusOjnHyFPOYGxlrOsULJ'): ") remote_server = input("\nEnter the remote servers URL without the 'https://' (Example: matrix.org): ") # find filesystem_id from database command_collect_filesystem_id = "ssh " + hardcoded_variables.homeserver_url + """ "/matrix/postgres/bin/cli-non-interactive --dbname=synapse -t -c 'SELECT DISTINCT filesystem_id FROM remote_media_cache WHERE media_id = '\\''""" + media_id + """'\\'" | xargs""" print(command_collect_filesystem_id) process_collect_filesystem_id = subprocess.run([command_collect_filesystem_id], shell=True, stdout=subprocess.PIPE, universal_newlines=True) filesystem_id = process_collect_filesystem_id.stdout print(process_collect_filesystem_id.stdout) # list the target files on disk command_collect_thumbnails = "ssh " + hardcoded_variables.homeserver_url + ' "find /matrix/synapse/storage/media-store/remote_thumbnail/' + remote_server + '/' + filesystem_id[:2] + "/" + filesystem_id[2:4] + "/" + filesystem_id[4:].rstrip() + """ -type f -printf '%p\\n'\"""" print(command_collect_thumbnails) process_collect_thumbnails = subprocess.run([command_collect_thumbnails], shell=True, stdout=subprocess.PIPE, universal_newlines=True) remote_thumbnails_list = process_collect_thumbnails.stdout print(remote_thumbnails_list) command_content_location = "ssh " + hardcoded_variables.homeserver_url + ' "ls /matrix/synapse/storage/media-store/remote_content/' + remote_server + '/' + filesystem_id[:2] + "/" + filesystem_id[2:4] + "/" + filesystem_id[4:].rstrip() + '"' print(command_content_location) process_content_location = subprocess.run([command_content_location], shell=True, stdout=subprocess.PIPE, universal_newlines=True) remote_content_location = process_content_location.stdout print(remote_content_location) # Zero the target files on disk then chattr +i them for line in remote_thumbnails_list.split('\n'): if line: command_zero_thumbnails = 'ssh ' + hardcoded_variables.homeserver_url + ' "true > ' + line + '"' print(command_zero_thumbnails) process_zero_thumbnails = subprocess.run(command_zero_thumbnails, shell=True) print(process_zero_thumbnails.stdout) command_make_thumbnail_immutable = 'ssh ' + hardcoded_variables.homeserver_url + ' "chattr +i ' + line + '"' print(command_make_thumbnail_immutable) process_make_thumbnail_immutable = subprocess.run(command_make_thumbnail_immutable, shell=True) print(process_make_thumbnail_immutable.stdout) command_zero_media = 'ssh ' + hardcoded_variables.homeserver_url + ' "true > ' + remote_content_location.rstrip() + '"' print(command_zero_media) process_remove_media = subprocess.run(command_zero_media, shell=True) print(process_remove_media.stdout) command_make_content_immutable = 'ssh ' + hardcoded_variables.homeserver_url + ' "chattr +i ' + remote_content_location.rstrip() + '"' print(command_make_content_immutable) process_make_content_immutable = subprocess.run(command_make_content_immutable, shell=True) print(process_make_content_immutable.stdout) # Example, first use the media_id to find the filesystem_id: # $ ssh matrix.perthchat.org "/matrix/postgres/bin/cli-non-interactive --dbname=synapse -t -c 'SELECT DISTINCT filesystem_id FROM remote_media_cache WHERE media_id = '\''eDmjusOjnHyFPOYGxlrOsULJ'\'" | xargs # ehckzWWeUkDhhPfNFkcfCFNv # Then use that filesystem_id to locate the remote file and all it's thumbnails: # $ ssh matrix.perthchat.org "find /matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv -type f -printf '%p\n'" #/matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv/32-32-image-jpeg-crop #/matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv/640-480-image-jpeg-scale # ... # $ ssh matrix.perthchat.org "ls /matrix/synapse/storage/media-store/remote_content/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv" # /matrix/synapse/storage/media-store/remote_content/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv # Then zero each file and make it immutable: # $ ssh matrix.perthchat.org "true > /matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv/32-32-image-jpeg-crop" # $ ssh matrix.perthchat.org "chattr +i /matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv/32-32-image-jpeg-crop" # $ ssh matrix.perthchat.org "true > /matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv/640-480-image-jpeg-scale" # $ ssh matrix.perthchat.org "chattr +i /matrix/synapse/storage/media-store/remote_thumbnail/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv/640-480-image-jpeg-scale" # ... # $ ssh matrix.perthchat.org "true > /matrix/synapse/storage/media-store/remote_content/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv" # $ ssh matrix.perthchat.org "chattr +i /matrix/synapse/storage/media-store/remote_content/matrix.org/eh/ck/zWWeUkDhhPfNFkcfCFNv" def purge_remote_media_repo(): purge_from = int(input("\nEnter the number of days to purge from: ")) purge_too = int(input("\nEnter the number of days to purge too: ")) while purge_from >= purge_too: # Calculate the epoch timestamp for 'purge_from' days ago epoch_time = int((datetime.datetime.now() - datetime.timedelta(days=purge_from)).timestamp()) # Convert to milliseconds (as per your original code) epoch_time_millis = epoch_time * 1000 # Make the request headers = {"Authorization": "Bearer " + hardcoded_variables.access_token} url = f"https://{hardcoded_variables.homeserver_url}/_synapse/admin/v1/purge_media_cache" params = {"before_ts": epoch_time_millis} response = requests.post(url, headers=headers, params=params) print(response.text) purge_from -= 1 time.sleep(2) # This loop is quite slow, our server was having disk issues. print("Done! :)") # Example: # $ date --date '149 days ago' +%s # 1589442217 # $ curl -X POST --header "Authorization: Bearer ACCESS_TOKEN" 'https://matrix.perthchat.org/_synapse/admin/v1/purge_media_cache?before_ts=1589439628000' def prepare_database_copy_of_multiple_rooms(): print("Preparing database copying of events from multiple rooms selected\n") print("This command needs to be run on the target server as root, it will setup postgres commands to download the join-leave events and all-events from a list of rooms.\n\nIt mounts a ramdisk beforehand at /matrix/postgres/data/ramdisk\n\nThis function is only compatible with Spantaleevs Matrix deploy script: https://github.com/spantaleev/matrix-docker-ansible-deploy\n") database_copy_list_location = input("Please enter the path of the file containing a newline seperated list of room ids: ") with open(database_copy_list_location, newline='') as f: reader = csv.reader(f) data = list(reader) make_ramdisk_command = "mkdir /matrix/postgres/data/ramdisk; mount -t ramfs -o size=512m ramfs /matrix/postgres/data/ramdisk; chown -R matrix:matrix /matrix/postgres/data/ramdisk" make_ramdisk_command_process = subprocess.run([make_ramdisk_command], shell=True, stdout=subprocess.PIPE, universal_newlines=True) print(make_ramdisk_command_process.stdout) x = 0 while x <= (len(data) - 1): print(data[x][0]) roomid_trimmed = data[x][0] roomid_trimmed = roomid_trimmed.replace('!', '') roomid_trimmed = roomid_trimmed.replace(':', '-') os.mkdir("/matrix/postgres/data/ramdisk/" + roomid_trimmed) touch_command = "touch /matrix/postgres/data/ramdisk/" + roomid_trimmed + "/dump_room_data.sql" touch_command_process = subprocess.run([touch_command], shell=True, stdout=subprocess.PIPE, universal_newlines=True) print(touch_command_process.stdout) sql_file_contents = "\set ROOMID '" + data[x][0] + "'\nCOPY (SELECT * FROM current_state_events JOIN room_memberships ON room_memberships.event_id = current_state_events.event_id WHERE current_state_events.room_id = :'ROOMID') TO '/var/lib/postgresql/data/ramdisk/" + roomid_trimmed + "/user_join-leave.csv' WITH CSV HEADER;\nCOPY (SELECT * FROM event_json WHERE room_id=:'ROOMID') TO '/var/lib/postgresql/data/ramdisk/" + roomid_trimmed + "/room_events.csv' WITH CSV HEADER;" print(sql_file_contents) sql_file_location = "/matrix/postgres/data/ramdisk/" + roomid_trimmed + "/dump_room_data.sql" sql_file = open(sql_file_location,"w+") sql_file.write(sql_file_contents) sql_file.close() x += 1 #print(x) time.sleep(1) chown_command = "chown -R matrix:matrix /matrix/postgres/data/ramdisk; docker restart matrix-postgres" chown_command_process = subprocess.run([chown_command], shell=True, stdout=subprocess.PIPE, universal_newlines=True) print(chown_command_process.stdout) print("\nThe sql query files have been generated, as postgres user in container run:\n# docker exec -it matrix-postgres /bin/bash\nbash-5.0$ export PGPASSWORD=your-db-password\nbash-5.0$ for f in /var/lib/postgresql/data/ramdisk/*/dump_room_data.sql; do psql --host=127.0.0.1 --port=5432 --username=synapse -w -f $f; done\n\nAfter copying the data to a cloud location law enforcement can access, clean up the ramdisk like so:\n# rm -r /matrix/postgres/data/ramdisk/*\n# umount /matrix/postgres/data/ramdisk") def get_reported_events(limit=100, _from=0, dir='b', user_id=None, room_id=None): url = f"https://{hardcoded_variables.homeserver_url}/_synapse/admin/v1/event_reports" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {hardcoded_variables.access_token}" } params = { 'limit': limit, 'from': _from, 'dir': dir } if user_id: params['user_id'] = user_id if room_id: params['room_id'] = room_id response = requests.get(url, headers=headers, params=params) if response.status_code == 200: return response.json() else: print(f"Error fetching reported events: {response.status_code}, {response.text}") return None def paginate_reported_events(limit=100, dir='b', user_id=None, room_id=None): _from = 0 all_reports = [] while True: reports = get_reported_events(limit=limit, _from=_from, dir=dir, user_id=user_id, room_id=room_id) if not reports or "event_reports" not in reports: break all_reports.extend(reports["event_reports"]) if "next_token" in reports: _from = reports["next_token"] else: break return all_reports def get_event_report_details(preset_report_id=''): if preset_report_id == '': report_id = input("\nEnter the report_id of the report you wish to query (Example: 56): ") elif preset_report_id != '': report_id = preset_report_id url = f"https://{hardcoded_variables.homeserver_url}/_synapse/admin/v1/event_reports/{report_id}" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {hardcoded_variables.access_token}" } response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() else: print(f"Error fetching event report details: {response.status_code}, {response.text}") return None def send_server_notice(preset_user_id='', preset_message='', txnId=None, event_type="m.room.message", state_key=None): """ Sends a server notice to a given user. Args: - user_id (str): The Matrix ID of the user to send the notice to, e.g. "@target_user:server_name". - message (str): The message to be sent as a notice. - txnId (str, optional): A unique transaction ID. If provided, retransmissions with the same txnId will be ignored. - event_type (str, optional): The type of event. Defaults to "m.room.message". - state_key (str, optional): Setting this will result in a state event being sent. Returns: - dict: A dictionary containing the response from the server. """ # Take user_id from user if not provided if preset_user_id == '': user_id = input("\nEnter the user_id of the user you would like to send the server notice to: ") elif preset_user_id != '': user_id = preset_user_id # Take message from user if not provided if preset_message == '': message = input("\nEnter the message you would like to send to the user: ") elif preset_message != '': message = preset_message # Construct the URL based on whether a txnId is provided if txnId: url = f"https://{hardcoded_variables.homeserver_url}/_synapse/admin/v1/send_server_notice/{txnId}" else: url = f"https://{hardcoded_variables.homeserver_url}/_synapse/admin/v1/send_server_notice" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {hardcoded_variables.access_token}" } # Construct the request body data = { "user_id": user_id, "content": { "msgtype": "m.text", "body": message } } if event_type: data["type"] = event_type if state_key: data["state_key"] = state_key # Send the request response = requests.put(url, headers=headers, json=data) if txnId else requests.post(url, headers=headers, json=data) if response.status_code == 200: return response.json() else: print(f"Error sending server notice: {response.status_code}, {response.text}") return None