Important Notice: this service will be discontinued by the end of 2024 because for multiple years now, Plume is no longer under active/continuous development. Sadly each time there was hope, active development came to a stop again. Please consider using our Writefreely instance instead.

Mastodon Followers und Following importieren. CircleCount für Arme

Bisher beschränkte ich mit auf Toots und nun sollen die eigenen Folgenden und Gefolgte - also die persönliche Vernetzung - in OpenSearch geladen werden

Einleitung

Natürlich hat das Mastodon UI schon eine recht gute Verwaltung, der Followers und Following, aber es wird spannend sein, wenn man die Informationen auch mal lokal hat. Man hat da mehr Möglichkeiten, was die Verfolgung von Veränderungen betrifft, man kann man die Interessengebiete der eigenen Bubble anschauen oder schlicht ein regelmäßiges Backup machen.

Es gab im OpenSearch Blog bereits zwei vorbereitende Artikel, die man sich definitiv anschauen muss.

  1. Index Templates in OpenSearch
  2. Index mit IndexTemplate erzeugen

Mit den Index-Templates haben wir eine Vorlage angelegt, die für alle neue Indexe verwendet werden. Im zweiten Artikel greifen wir auf das Template zurück und legen zwei Indexe an: followers und following.

Dazu gibt es noch diese beiden Grundlagen-Artikel:

  1. Mastodon Instanz API nutzen - Erste Schritte
  2. Mit Python auf Mastodon eine Nachricht senden

In dem Einführungs-Artikel wird unsere App (also unsere Sammlung an Python Scripten) registriert. Diese Registrierungsdaten benötigen wir, da wir nicht mehr auf die Public API zugreifen.

Im zweiten Artikel wird zwar beschrieben, wie man einen Toot absendet, aber auch wie man sich mit einem User-Account anmeldet. Das ist für unser kommendes Beispiel wichtig, weil wir von unserem Account die Vernetzung der Accounts lesen wollen.

Jepp, ganz schön viele Bausteine, die man vorab braucht, aber ihr folgt hier einem großen Plan ;-) - alle Artikel haben ihren Sinn und werden Stück für Stück zusammengefügt.

User-Account Anmeldung

Das Script-Präfix beginnt diesmal damit auch anders:

import json
from mastodon import Mastodon
from opensearchpy import OpenSearch


def login_os(host, port):
    # Client zu dem Dev Cluster (ohne SSL, ohne Anmeldung)
    return OpenSearch(
        hosts=[{'host': host, 'port': port}],
        http_compress=True,  # enables gzip compression for request bodies
        use_ssl=False
    )


def login_mastodon(url, user, password):
    mastodon = Mastodon(
        client_id='ep********************************************Xo', # Use your registred ID
        client_secret='AY******************************************0s',  # Use your registred secret
        api_base_url=url
    )

    mastodon.access_token = mastodon.log_in(
        username=user,
        password=password,
        scopes=['read', 'write']
    )

    return mastodon

Wichtig ist, dass die Registrierungsdaten eurer App da oben eingetragen werden.

Um zu schauen, ob das alles klappt, mal diese Scriptzeilen anfügen:

client = login_os('localhost', 9200)
mastodon = login_mastodon('https://social.tchncs.de', '<your email-address>', '<your secret password>')

# It's me:
me = mastodon.me()
print (f"Me: {me['id']} = {me['username']}")

Und wieder der Hinweis: Da oben die Mailadresse und Kennwort des persönlichen Accounts.

Zusammen ausgeführt, wird eine ID und ein Benutzername ausgegeben - oder es hagelt Fehlermeldungen. Vertippt? App wirklich registriert? Bitte das erst korrigieren, dann geht es hier weiter:

Followers laden und speichern

Im letzten Blog-Artikel haben wir zwei Indexe angelegt: followers und following. Die wollen wir nun mit unseren geliebten Mutuals füllen - und zwar allen.

Bei den Toots hatten wir immer eine Begrenzung der Ladevorgänge der Seiten, das machen wir nicht. Wir haben eine Endlosschleife. Mein erster Versuch sah so aus (und funktioniert auch):

page = None
while True:
    page = mastodon.account_followers(id=me['id'], limit=80) if page is None else mastodon.fetch_next(page)
    if page:
        for account in page:
            client.index('followers', id=account['id'], body=account)
    else:
        break

Bestimmt hat jemand eine bessere Idee für einen Schleife oder Iterator, aber das ist erstmal eingängig.

Allerdings müssten wir das zweimal programmieren (für followers und following), weil die Mastodon API, zwei verschiedene Methoden zum Laden nutzt.

Softwareentwickler sind aber von Haus aus wirklich faul und nichts ist schlimmer als redundanter Code, wo nur eine Zeile unterschiedlich ist. Die Lösung sind Callback-Funktionen. Auch Python beherrscht das und wir basteln uns mal eine Funktion:

Accounts laden und speichern

def load_to_index(client, index_name, initial_load, next_load):
    page = None
    while True:
        page = initial_load(id=me['id'], limit=80) if page is None else next_load(page)
        if page:
            for account in page:
                client.index(index_name, id=account['id'], body=account)
        else:
            break

client ist unser OpenSearch Objekt, index_name unser Index. Dann kommen die Callback-Parameter: initial_load und next_load.

Das wird auch sehr einfach aufgerufen:

load_to_index(client, 'followers', mastodon.account_followers, mastodon.fetch_next)
load_to_index(client, 'following', mastodon.account_following, mastodon.fetch_next)

Das ist schon ziemlich cool.

Beim Import möchte ich aber sehen, was passiert. Also kommt etwas geschwätzige Statistik hinzu. Die nächste Variante:

def load_to_index(client, index_name, initial_load, next_load):
    print(f'Import {index_name}')
    page = None
    created = 0
    updated = 0
    while True:
        page = initial_load(id=me['id'], limit=80) if page is None else next_load(page)
        if page:
            for account in page:
                response = client.index(index_name, id=account['id'], body=account)
                if response['result'] == "created":
                    print(f"   New in {index_name}: {account['acct']}")
                    created += 1
                elif response['result'] == "updated":
                    updated += 1
        else:
            break

    client.indices.refresh(index=index_name)
    print(f'Finished "{index_name}". Created {created} accounts and updated {updated}')

Ja, das Refresh dürfen wir nicht vergessen und abschließend eine Ausgabe, was passiert ist.

Ok, bekommt ihr das selber zusammengesetzt?

Als Ergebnis sollte man das sehen:

Import followers
Finished "followers". Created 197 accounts and updated 0
Import following
Finished "following". Created 139 accounts and updated 0

Ruft man das Script erneut auf:

Import followers
Finished "followers". Created 0 accounts and updated 197
Import following
Finished "following". Created 0 accounts and updated 139

Wenn man das alle paar Tage mal macht und das Glück hat mehr Follower zu bekommen, erhöht sich die Zahl kontinuierlich.

Allerdings haben wir auch ein Problem mit dieser simplen Art, von Mastodon die Accounts zu synchronisieren. Welches? Das kommt im nächsten Artikel. Ja ja, der Cliffhanger - da ist er wieder.