commit 7be12197f0e32abc55864dbf97c44ffd2b45e9af Author: shibao Date: Fri May 16 20:35:07 2025 +0000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdaab25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +env/ diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..aa17ac3 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.11.5 diff --git a/misskey_scripting.py b/misskey_scripting.py new file mode 100644 index 0000000..5ddcec7 --- /dev/null +++ b/misskey_scripting.py @@ -0,0 +1,119 @@ +#!/usr/bin/python3 + +import json +import requests + +# Change these configs +TOKEN = "misskey-api-token" +INSTANCE = "https://your-instance.com" + + +def post(path, body): + """Sends a post request to the Misskey instance.""" + return requests.post( + f"{INSTANCE}/api{path}", + headers={ + "Authorization": f"Bearer {TOKEN}", + "Content-Type": "application/json", + }, + data=json.dumps(body), + timeout=10, + ) + + +def fetch_notes(user_id, until_id=None): + """Fetches all notes from a user.""" + if until_id: + notes = post( + "/users/notes", + { + "userId": user_id, + "withFiles": False, + "withReplies": True, + "withChannelNotes": True, + "limit": 100, + "untilId": until_id + }, + ) + else: + notes = post( + "/users/notes", + { + "userId": user_id, + "withFiles": False, + "withReplies": True, + "withChannelNotes": True, + "limit": 100 + }, + ) + return notes.json() + + +def fetch_users(target_instance, until_id=None): + """Gets all users for a given Misskey instance.""" + if until_id: + users = post( + "/federation/users", + {"limit": 100, "host": target_instance, "untilId": until_id}, + ) + else: + users = post("/federation/users", {"limit": 100, "host": target_instance}) + return users.json() + + +def destroy(target_instance): + """Destroys all accounts on the given Misskey instance.""" + users = fetch_users(target_instance) + + while users: + for user in users: + destroy_notes(user['id']) + r1 = post("/admin/delete-account", {"userId": user['id']}) + r2 = post("/admin/delete-all-files-of-a-user", {"userId": user['id']}) + if r1.status_code == 204 and r2.status_code == 204: + print(f"Deleted @{user['username']}@{user['host']}") + else: + print(f"Failed to delete @{user['username']}@{user['host']}!!!!!") + + users = fetch_users(target_instance, users[-1]['id']) + + +def destroy_notes(user_id): + """Destroys all accounts on the given Misskey instance.""" + notes = fetch_notes(user_id) + + while notes: + for note in notes: + r = post("/notes/delete", {"noteId": note['id']}) + if r.status_code == 204: + print(f"Deleted {print_name(note)}: {print_note(note)}") + else: + print(f"Could not delete {print_name(note)}: {print_note(note)}") + + notes = fetch_notes(user_id, notes[-1]['id']) + +def print_note(note): + if note.get('renote') and note['renote'].get('reactions'): + return "Reaction" + elif note.get('renote'): + return f"Renote {print_note(note['renote'])}" + elif note.get('files') or note.get('fileIds'): + return "File" + elif note.get('reactions'): + return "Reaction" + else: + if note['text'] is None: + print(note) + return truncate_text(note['text']) + +def print_name(note): + return f"@{note['user']['username']}@{note['user']['host']}" + +def truncate_text(text): + if text is None: + return "None" + else: + return text[:75].replace("\n", " ") + + +destroy("instance-to-destroy.com") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bccde58 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Requests