|
@@ -0,0 +1,131 @@
|
|
|
+import json
|
|
|
+import logging
|
|
|
+from datetime import datetime, timedelta
|
|
|
+from mastodon import Mastodon
|
|
|
+from config import *
|
|
|
+from valid import valid_accounts
|
|
|
+from clean import clean_accounts
|
|
|
+
|
|
|
+
|
|
|
+def fetch_accounts(api_function):
|
|
|
+ accounts = api_function()
|
|
|
+ accounts = mastodon.fetch_remaining(accounts)
|
|
|
+ accounts = [
|
|
|
+ acc
|
|
|
+ for acc in accounts
|
|
|
+ if acc["last_status_at"] is None # have no posts
|
|
|
+ or acc["last_status_at"] < valid_date # inactive accounts
|
|
|
+ ]
|
|
|
+ return accounts
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ logging.basicConfig(level=logging.INFO)
|
|
|
+ logging.info("Starting Fedi Cleaner")
|
|
|
+ mastodon = Mastodon(
|
|
|
+ access_token=ACCESS_TOKEN, api_base_url=API_BASE_URL, ratelimit_method="pace"
|
|
|
+ )
|
|
|
+
|
|
|
+ current_user = mastodon.me()
|
|
|
+ uid = current_user["id"]
|
|
|
+ following, followers, lists_members, blocks, mutes = [], [], [], [], []
|
|
|
+ valid_date = datetime.now() - timedelta(days=INACTIVE_DAYS) # FIXME: UTC
|
|
|
+
|
|
|
+ if CLEAN_FOLLOWING or not CLEAN_MUTUALS:
|
|
|
+ logging.info("Fetching following")
|
|
|
+ following = fetch_accounts(lambda: mastodon.account_following(uid))
|
|
|
+
|
|
|
+ if CLEAN_FOLLOWERS or not CLEAN_MUTUALS:
|
|
|
+ logging.info("Fetching followers")
|
|
|
+ followers = fetch_accounts(lambda: mastodon.account_followers(uid))
|
|
|
+
|
|
|
+ if CLEAN_LISTS: # in akkoma/pleroma list members can be unfollowed
|
|
|
+ logging.info("Fetching lists")
|
|
|
+ lists = mastodon.lists()
|
|
|
+ for list_ in lists:
|
|
|
+ list_members = fetch_accounts(lambda: mastodon.list_accounts(list_["id"]))
|
|
|
+ lists_members.append(
|
|
|
+ {"id": list_["id"], "title": list_["title"], "members": list_members}
|
|
|
+ )
|
|
|
+
|
|
|
+ if CLEAN_BLOCKS: # they can't follow us so there's no mutuals
|
|
|
+ logging.info("Fetching blocks")
|
|
|
+ blocks = fetch_accounts(mastodon.blocks)
|
|
|
+
|
|
|
+ if CLEAN_MUTES:
|
|
|
+ logging.info("Fetching mutes")
|
|
|
+ mutes = fetch_accounts(mastodon.mutes)
|
|
|
+
|
|
|
+ if not CLEAN_MUTUALS: # just don't want to use relationship api
|
|
|
+ logging.info("Excluding mutuals")
|
|
|
+ following_id = [acc["id"] for acc in following]
|
|
|
+ followers_id = [acc["id"] for acc in followers]
|
|
|
+ mutuals = list(set(following_id) & set(followers_id))
|
|
|
+ # mutuals are excluded
|
|
|
+ following = [acc for acc in following if acc["id"] not in mutuals]
|
|
|
+ followers = [acc for acc in followers if acc["id"] not in mutuals]
|
|
|
+ mutes = [acc for acc in mutes if acc["id"] not in mutuals]
|
|
|
+ for list_ in lists_members:
|
|
|
+ list_["members"] = [
|
|
|
+ acc for acc in list_["members"] if acc["id"] not in mutuals
|
|
|
+ ]
|
|
|
+
|
|
|
+ accounts = {
|
|
|
+ "following": following if CLEAN_FOLLOWING else None,
|
|
|
+ "followers": followers if CLEAN_FOLLOWERS else None,
|
|
|
+ "lists_members": lists_members if CLEAN_LISTS else None,
|
|
|
+ "blocks": blocks if CLEAN_BLOCKS else None,
|
|
|
+ "mutes": mutes if CLEAN_MUTES else None,
|
|
|
+ }
|
|
|
+
|
|
|
+ # if DRY_RUN:
|
|
|
+ # with open("accounts.json", "w") as f:
|
|
|
+ # f.write(
|
|
|
+ # json.dumps(
|
|
|
+ # accounts,
|
|
|
+ # indent=4,
|
|
|
+ # default=str,
|
|
|
+ # )
|
|
|
+ # )
|
|
|
+
|
|
|
+ # now validate those accounts
|
|
|
+ # criteria: 1. account migration 2. inactive account 3. dead instance or errors
|
|
|
+ criteria = {
|
|
|
+ 1: CLEAN_MIGRATED_ACCOUNTS,
|
|
|
+ 2: CLEAN_INACTIVE_ACCOUNTS,
|
|
|
+ 3: CLEAN_DEAD_ACCOUNTS,
|
|
|
+ }
|
|
|
+ operations = {}
|
|
|
+ for k, v in accounts.items():
|
|
|
+ logging.info(f"Validating {k}")
|
|
|
+ if v is None:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if k == "lists_members":
|
|
|
+ operation = {
|
|
|
+ list_["id"]: [
|
|
|
+ id
|
|
|
+ for id, state in valid_accounts(list_["members"])
|
|
|
+ if criteria[state]
|
|
|
+ ]
|
|
|
+ for list_ in v
|
|
|
+ }
|
|
|
+ else:
|
|
|
+ operation = [id for id, state in valid_accounts(v) if criteria[state]]
|
|
|
+
|
|
|
+ operations[k] = operation
|
|
|
+
|
|
|
+ # now clean those accounts
|
|
|
+ # or only write operations to file
|
|
|
+ if DRY_RUN:
|
|
|
+ with open("operations.json", "w") as f:
|
|
|
+ f.write(
|
|
|
+ json.dumps(
|
|
|
+ operations,
|
|
|
+ indent=4,
|
|
|
+ default=str,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ clean_accounts(mastodon, operations)
|
|
|
+ logging.info("Done!")
|