Direkt zum Hauptbereich

Alte Skeets löschen

 Alte Skeets löschen

Freie Lizenz pixabay.de Monsterkoi


Ich bin ein Freund davon, meine Posts auf Social Media nicht zu alt werden zu lassen. Telegram und Mastodon, bieten das mit eigenen Werkzeugen an. Für Bluesky gibt es onlinedienste, wie den Skysweeper oder Repocleaner. Eine richtig saubere Löschung machen die aber nicht. 
Insbesondere dann nicht, wen Threads weggeräumt werden sollen.

Deswegen suchte ich nach Lösungen, möglichst mit PHP, die das Ganze erledigten. Ich bin kein wirklich guter Programmierer und nehme gerne das, was andere entwickelt haben. 

Mit PHP fand ich nichts, was ich verstanden hätte, dafür aber einige python-Scripts.
Python habe ich auf dem heimischen Windows-Rechner am Laufen und so sprach alles für einen Versuch.

Für beide Scripts ist es nötig, dass ihr für Euren Account ein App-Passwort generiert habt. Deleteskee ist der ältere. Skycleaner ist der Fork.

Beide laufen nicht ohne Modifikation, da sie den gleichen Fehler bei der Behandlung der Daten aufweisen.

Die bei Github bereitgestellten Requirements-Dateien müsst ihr in Eure python-Umgebung einlesen mit: pip install -r requirements.txt
Zusätzlich: pip install pytz

In der Praxis hat sich gezeigt, dass es mit deleteskee gerade bei den Threads  am besten läuft. Die Ausgabe ist halt gewöhnungsbedürftig. Aber das Ergebnis zählt am Meisten. 😄

Konfigurationsdatei Config.json 

Eine config.json, die für beide Skripte funktioniert sieht so aus:

{
  "username": "[Dein Handle].bsky.social",
  "password": "Dein App-Passwort",
  "days_to_keep": {
    "posts": 180,
    "reposts": 30,
    "boosts": 30
  }
}

Korrigiertes Deleteskee-Script (deleteskee.py)

from atproto import Client, AtUri
from datetime import datetime, timedelta
from pathlib import Path
import json
import os
import pytz

from datetime import datetime, timezone

class Config():
    def __init__(self):
        self.days_to_keep = {"posts": 14, "boosts": 7}

        cfgfile = Path.cwd() / "config.json"
        if cfgfile.exists():
            with cfgfile.open() as handle:
                cfg = json.load(handle)

            self.username = cfg.get("username", None)
            self.password = cfg.get("password", None)

            if "days_to_keep" in cfg:
                self.days_to_keep = cfg["days_to_keep"]
        else:
            self.username = os.environ["DELETESKEE_USERNAME"]
            self.password = os.environ["DELETESKEE_PASSWORD"]

            if "DELETESKEE_KEEP_POSTS" in os.environ:
                self.days_to_keep["posts"] = int(os.environ["DELETESKEE_KEEP_POSTS"])
            if "DELETESKEE_KEEP_BOOSTS" in os.environ:
                self.days_to_keep["boosts"] = int(os.envioron["DELETESKEE_KEEP_BOOSTS"])


config = Config()

cli = Client()
profile = cli.login(config.username, config.password)

def paginated_list_records(cli, repo, collection):
    params = {
        "repo": repo,
        "collection": collection,
        "limit": 100,
    }

    records = []
    while True:
        resp = cli.com.atproto.repo.list_records(params)

        records.extend(resp.records)

        if resp.cursor:
            params["cursor"] = resp.cursor
        else:
            break

    return records

now_aware = datetime.now(timezone.utc)
now = datetime.now()

post_delta = timedelta(days=config.days_to_keep["posts"])
post_check = now_aware - post_delta

boost_delta = timedelta(days=config.days_to_keep["boosts"])
boost_check = now_aware - boost_delta

records = {}
for collection in ["app.bsky.feed.post", "app.bsky.feed.repost"]:
    records[collection] = paginated_list_records(cli, config.username, collection)
    print(f"{collection}: {len(records[collection])}")


deletes = []
for collection, posts in records.items():
    if collection == "app.bsky.feed.post":
        check_date = post_check
    elif collection == "app.bsky.feed.repost":
        check_date = boost_check

    for post in reversed(posts):
        postdate = post.value.created_at
        if postdate.endswith("Z"):
            postdate = datetime.fromisoformat(post.value.created_at[:-1])
        else:          
            postdate = datetime.fromisoformat(post.value.created_at)
                                             
        if postdate.astimezone(timezone.utc) < check_date.astimezone(timezone.utc):
            uri = AtUri.from_str(post.uri)
            deletes.append({
                "$type": "com.atproto.repo.applyWrites#delete",
                "rkey": uri.rkey,
                "collection": collection,
            })
        else:
            break


print("going to delete", len(deletes), "posts/reposts")
if len(deletes) > 0:
    for i in range(0, len(deletes), 200):
        print(cli.com.atproto.repo.apply_writes({"repo": config.username, "writes": deletes[i:i+200]}))

for collection in ["app.bsky.feed.post", "app.bsky.feed.repost"]:
    records[collection] = paginated_list_records(cli, config.username, collection)
    print(f"{collection}: {len(records[collection])}")



Korrigiertes Skycleaner Script (cleaner.py)

from atproto import Client, AtUri
from datetime import datetime, timedelta, timezone
from pathlib import Path
import json
import pytz


class Config():
    def __init__(self):
        self.days_to_keep = {"posts": 0, "reposts": 0}

        cfgfile = Path.cwd() / "config.json"
        if cfgfile.exists():
            with cfgfile.open() as handle:
                cfg = json.load(handle)

            self.username = cfg.get("username", None)
            self.password = cfg.get("password", None)
            self.days_to_keep = cfg.get("days_to_keep", None)

config = Config()

cli = Client()
profile = cli.login(config.username, config.password)

def paginated_list_records(cli, repo, collection):
    params = {
        "repo": repo,
        "collection": collection,
        "limit": 100,
    }

    records = []
    while True:
        resp = cli.com.atproto.repo.list_records(params)

        records.extend(resp.records)

        if resp.cursor:
            params["cursor"] = resp.cursor
        else:
            break

    return records

now = datetime.now(timezone.utc)

post_delta = timedelta(days=config.days_to_keep["posts"])
post_hold_datetime = now - post_delta

repost_delta = timedelta(days=config.days_to_keep["reposts"])
repost_hold_datetime = now - repost_delta

records = {}
for collection in ["app.bsky.feed.post", "app.bsky.feed.repost"]:
    records[collection] = paginated_list_records(cli, config.username, collection)
    print(f"{collection}: {len(records[collection])}")


deletes = []
for collection, posts in records.items():
    if collection == "app.bsky.feed.post":
        hold_datetime = post_hold_datetime
    elif collection == "app.bsky.feed.repost":
        hold_datetime = repost_hold_datetime
    else:
        break

    for post in reversed(posts):
        # remove charactors on `created_at` behined of `Z`
        # z_index_in_created_at = post.value.created_at.index('Z')
        # post_created_at = datetime.fromisoformat(post.value.created_at[:z_index_in_created_at+1])
        post_created_at = post.value.created_at
        if post_created_at.endswith("Z"):
            post_created_at = datetime.fromisoformat(post.value.created_at[:-1])
        else:          
            post_created_at = datetime.fromisoformat(post.value.created_at)
 
        if post_created_at.astimezone(timezone.utc) <= hold_datetime.astimezone(timezone.utc):
            uri = AtUri.from_str(post.uri)
            deletes.append({
                "$type": "com.atproto.repo.applyWrites#delete",
                "rkey": uri.rkey,
                "collection": collection,
            })
        else:
           pass


print(f'{datetime.now()} COMMENCE DELETE: {len(deletes)} posts/reposts')
if len(deletes) > 0:
    for i in range(0, len(deletes), 200):
        cli.com.atproto.repo.apply_writes({"repo": config.username, "writes": deletes[i:i+200]})
print(f'{datetime.now()} DELETE COMPLETED')

Aufruf : python deleteskee.py

Ergebnis:

app.bsky.feed.post: 12
app.bsky.feed.repost: 10
going to delete 0 posts/reposts
app.bsky.feed.post: 12
app.bsky.feed.repost: 10

Ich hoffe, dass dieser Artikel dem Einen oder der Anderen hilft.

Kommentare

Beliebte Posts aus diesem Blog

Position

  Position Es fehlt ne klare Position im Strom der Zeit, ihr merkt es schon. Da wird gelogen, dass es kracht. Ein Land mit Fleiß [i] kaputt gemacht. In schwerem Sturm mit Ruderbruch Macht die Regierung den Versuch Kurs zu finden und zu halten Mit Werkzeugen, die früher galten. Man hat sich trefflich ausgeruht. Dem Land ging es zu lange gut. Jetzt weiß man nicht wie in Gefahr das Schiff denn zu führen war. Ohne Hilfe wird’s nicht klappen. Da geht zu vieles durch die Lappen. Doch Gott, der einst oft wurd‘ gefragt hat das letzte Wort gesagt. Man hält sich fest die Ohren zu lass mich mit deinem Gott in Ruh. Er helfe dir und unserm Staat. Ich höre nicht auf seinen Rat. Dabei kennt der kluge Leute. Die können wirklich helfen – Heute! Er kann helfen Kurs zu finden. Schwere Krisen überwinden. Er wartet nur, dass man ihn fragt. Es nicht wieder lang vertagt. Weil es Wichtigeres gibt, als einen Gott der Menschen liebt. (C) Christian-Michael Kleinau 12/2023 ...

Weihnachten mit voller Wucht

 Weihnachten mit voller Wucht Weihnachten traf Erich plötzlich und unerwartet mit voller Wucht. Irgendwie war alles, was im Vorfeld darauf hindeutete an ihm vorbeigegangen. Es war normal geworden, dass fast ein halbes Jahr lang irgendwelche Weihnachtsaccessoires verkauft und einschlägige Musik gespielt wurde. Und jetzt hatte er ein Problem. Die Familie erwartete seine Anwesenheit. Wenigstens an diesen besonderen Tagen sollte er Zeit für sie haben. Dabei hatte man sich praktisch schon seit Jahren auseinandergelebt. Er ärgerte sich. Irgendwie wollte er sich ja auch ein bisschen anpassen. Er hätte gerne noch ein paar Geschenke besorgt, aber jetzt, am 23. Dezember abends gab es dafür praktisch keine Chance mehr. Die Adventszeit war viel zu kurz gewesen. Jedoch fiel dieses Jahr Heiligabend auf einen Sonntag. Praktisch hatte man ihm eine Woche geklaut. Marie-Sophie befürchtete das Schlimmste, als Erich bei ihr anrief: „Ich kann leider nicht kommen. Bleibe lieber in meiner Wohnung in ...

LTE Backup mit der Fritzbox einrichten

  Was tun bei längerem Internetaufsall? Ein mehrtägiger Ausfall der Internetverbindung der Deutschen Giganetz vom 29.8.2023-31.8.2023 führte zu der Frage nach einer redundanten Absicherung der Internetverbindung. Diese Anleitung funktioniert nicht mit der von der Deutschen Giganetz gelieferten Fritzbox 7430 AX! Möglicherweise aber mit der Fritzbox 7430 . AVM hat auf seiner Hilfeseite die 7530 AX für das Verfahren nicht erwähnt.  Ich habe meine eigene schon vorher im Einsatz befindliche Fritzbox 7590 dafür reaktiviert.  Zutaten LTE Stick                      (Ich habe einen mit Huawei E3372-325 Chipsatz) aktivierte SIM-Karte   (Ich verwende Alditalk mit dem Alditalk Mini-Tarif 2,99€/Monat) AVM Fritzbox 7590 LTE-Stick konfigurieren Die meisten aktuellen LTE-Sticks können nicht von der Fritzbox verwaltet werden und stellen ihre Verbindung autark her. Deswegen muss zuerst der Stick konfiguriert werden. Dies kl...