Posts by beandev@write.tchncs.de
(DIR) Post #AJ4Rq3STP4YeCCSbCa by beandev@write.tchncs.de
2022-05-03T09:23:22.195194Z
0 likes, 1 repeats
Vor kurzer Zeit fragte ich, ob es neben Plume noch andere bekanntere Blogsoftware (und Dienste) für das Fediverse gibt. Allerdings gibt es nicht viel, außer vielleicht WriteFreely. Sogar Plume schreibt selbst auf https://joinplu.me/, dass die Software gerade nicht sehr aktuell ist und die Entwicklung stockt. Trotzdem erscheint mir gerade die Übersichtlichkeit der Funktionen besonders attraktiv. Zudem gibt es wohl doch ein klein wenig Entwicklungsarbeiten an Plume, da in diesem Jahr schon die Versionen 0.7.0 und 0.7.1 erschienen sind und die Integration in Mastodon verbessert.Ich brauche nicht mehr als ein wenig Markdown und etwas Syntax-Highlighting:world='Fediverse'print (f"Hello {world}")Das scheint auch gut zu funktionieren.In diesem Blog soll es etwas um Mastodon gehen. Primär aus technischer Sicht (also API, Entwicklung, Scripting usw.), aber auch News und gesellschaftliche wie soziale Themen rund ums Fediverse.Es ist auch ein Experiment, wie es sich mit Blogs im Fediverse anfühlt und wie es sich integriert. Neben dem Themenschwerpunkt Mastodon & Python, werde ich ggf. weitere Blogs mit anderen Themen aktivieren und alles mal dort sammeln, was in Toots über die Zeit verloren gehen.
(DIR) Post #AJ4UbjKLkkXRZKWPIW by beandev@write.tchncs.de
2022-05-03T09:54:24.890791Z
0 likes, 0 repeats
EinführungMastodon hat eine sehr saubere und klare REST API, für eine Menge an Operationen. Im Prinzip alles, was man in einem Client durchführen kann (Lesen, Nachrichten schreiben, Favorisieren, Bilder hochladen, usw. usf.) lässt sich ebenfalls über die API realisieren. Allerdings erfordert das natürlich eine Authentifizierung. Ohne sich der Instanz “Bekanntzumachen”, kann man nicht auf die API zugreifen.Mastodon verwendet dafür OAuth2, was sich deutlich von einer User/Password Authentifizierung, wie es BasicAuth erlaubt, unterscheidet. Ohne tiefer ins Detail zu gehen, ist es wichtig zu wissen, dass OAuth2 verschiedene Anmelde-Workflows bietet, die stufenweise funktionieren. Man muss sich das einfach so vorstellen: Da man als Mensch ja nicht direkt mit der Maschine “Mastodon-Instanz” (dem Host) reden kann, benutzt man immer einen Stellvertreter, eine Application (der Client). Ein Client, kann eine Smartphone-App wie Tusky oder Fedilab sein, es kann das Mastodon WebUI sein oder eben auch ein Python-Script.Eine Client-App, die mit einem Host sprechen will, muss damit sich erstmal registrieren. Ansonsten akzeptiert der Host den Client nicht. Das funktioniert bei Mastodon auf zwei Arten. Entweder selbst über die API (denn über die API darf man alles machen) oder man registriert über das Mastodon WebUI eine Client Application. Dass sich eine App einfach selber registrieren darf, ist übrigens nicht selbstverständlich. Im Fediverse ist das bei dezentralen Instanzen und den vielen Client-Implementationen aber deutlich einfacher zu handhaben. Man stelle sich vor jede App (Tusky, Fedilab, Mastodon, …) müsste auf jeder Instanz in Fediverse erstmal manuell von jedem Anwender oder Entwickler registriert werden, damit man Nachrichten schreiben kann! Das wäre eine riesige Hürde, die man in einem offenen Netzwerk so nicht haben will.Wichtig ist zu wissen, dass mit der Registrierung einer App, die Mastodon Instanz sich diese Information merkt und dem Registrierenden ein paar Daten mitgibt, die man immer wieder verwenden sollte. Damit kommen wir zu wichtigen Merkmalen von OAuth2. Die App bekommt eine ID (Client-ID) und ein Geheimnis (Client-Secret) zugewiesen. Diese beiden Informationen reichen aber noch nicht, um tatsächlich auf die API zuzugreifen. Es gibt noch ein Zugangstoken. Ein Token ist eine zeitlich begrenzter Zugangsschlüssel. So wie eine Hotelkarte, mit der man nur zu den gebuchten Tagen in das Hotelzimmer darf, ist ein Zugangstoken ebenfalls auf eine bestimmte Zeit beschränkt. Ist der Token abgelaufen, muss man sich einen neuen besorgen. Das ist ein recht aufwendiger Arbeitsablauf und erfordert ein paar Schritte. Zunächst registriert man sich als Application. Dafür denkt man sich einen Namen aus und gibt eine URL der eigenen Heimat an (oder man verwendet einen speziellen Platzhalter). Dann bekommt man eine ID, ein Secret und einen Token. Mit den drei Informationen darf man dann die ersten Abfragen durchführen. Da das immer wiederkehrende Arbeiten sind, verlässt man sich dabei besser auf Bibliotheken, die das für einen machen. Für Python gibt es Mastodon.py. Diese Bibliothek ist nicht nur ein Wrapper für die gesamte REST-API, sie kümmert sich auch um den ganzen Workflow der Registrierung und Authentifizierung. Es ist sozusagen der persönliche Hotelpage, der einem bei allem hilft, inkl. dem Öffnen der Türen.Zwar erlaubt auch Mastodon.py das direkte Registrieren per API, aber es ist sinnvoller, das über das Web-UI zu machen. In dem Fall merkt sich das UI die Registrierung und speichert die Daten für einen. Nicht jede Instanz erlaubt das, aber die meisten. Application Client registrierenGrunddatenZunächst geht man über das WebUi in Einstellungen und dort auf Entwicklung:Mit dem Button “Neue Anwendung” lässt sich nun eine Client-App registrieren:Als Namen denkt man sich was Nettes aus, die URL kann das eigene Instanz-Profil sein und die Weiterleitungs-URL sollte mit dem Platzhalter “urn:ietf:wg:oauth:2.0:oob” beschrieben sein. Wir schreiben Scripte und werden keine Login-Eingaben an einen Benutzer weiterleiten. BerechtigungenIm untereren Bereich kann man bei “Befugnissen” festlegen, was die Client Application überhaupt alles darf. In OAuth2 nennt man das den “Scope”. Für die ersten Spielereien sollte man nur den Haken bei “read” aktiviert haben, damit man sich nicht aus Versehen was kaputt macht. Also “write” und “follow” erstmal deaktivieren. Man sieht aber, dass man sich sehr viele Einzelberechtigungen zusammenstellen kann:OAuth2 Daten der RegistrierungNachdem man alles mit dem großen blauen Speicherbutton am Ende gesichert hat, ist erstmal Verwirrung angesagt. Nirgendwo sieht man nun die Client-ID und das Secret und auch kein Token. Dafür nun in der Liste der Anwendungen wieder auf den Namen klicken, und voilà: die OAuth2 Daten:Mastodon.py installierenIch gehe davon aus, dass Grundkenntnisse in Python existieren und vor allem eine Umgebung existiert, in Python zu entwickeln. Viele werden pip oder pip3 verwenden, also ist Mastodon.py schnell installiert.pip3 install Mastodon.pyDas erste ScriptNun die IDE der Wahl öffnen und ein superkleines Beispiel (noch nicht vollständig):import jsonfrom mastodon import Mastodonmastodon = Mastodon ( client_id='ep***************************************Xo', client_secret='AY**********************************0s', api_base_url='https://social.tchncs.de')Wir brauchen “json” für bequemere Ausgaben und dann wird das installierte Mastodon.py importiert. Im nächsten Schritt wird das Mastodon-Object erzeugt und wir übergeben die client_id und das client_secret (aus dem Web-UI sind das Client-Schlüssel und Client-Secret). Die api_base muss die Instanz sein, wo wir die App registriert haben.Ups, warum nicht das Token? Wir lassen unseren Hotelpagen “Mastodon.py” den Job machen. Fehlt der Token, fragt Mastodon.py direkt nach einem neuen Token an. Etwas verschwenderisch, aber hier sinnvoll. Denn der Token läuft sowieso irgendwann ab und wir wollen ja nicht jedes Mal das Script ändern…Nun fügen wir aber noch was dem Script an:nodeinfo = mastodon.instance_nodeinfo()print (json.dumps(nodeinfo, indent=4, default=str))activity = mastodon.instance_activity()print (json.dumps(activity, indent=4, default=str))Node-Info gibt eine kleine Information der Instanz zurück, Activity eine Statistik darüber, was eigentlich so los ist. Es muss beachtet werden, dass manche Instanzen diese Abfragen nicht erlauben. Dann hagelt es Fehlermeldungen. Was abschließend noch wichtig ist. Wir haben hiermit auf öffentliche Daten der Instanz zugegriffen. Die Registrierung der Client-App (also unseres Scriptes) wurde zwar über das Web-UI (unter unserem Account) angelegt, aber wir verwendeten hier noch keinerlei Benutzerdaten. Das war für diese API Aufrufe auch nicht notwendig, weil wir keine benutzerbezogenen Aktionen ausführten. Es ist sogar möglich über den Mastodon.py Wrapper sich nur als App zu registrieren und die Informationen abzurufen. Das ist eine sehr offene Art, mit einer API umzugehen. Allerdings gibt es immer Einschränkungen. Jede Instanz kann selbst entscheiden, was abrufbar ist und auch die Häufigkeit der Abfragen ist limitiert. Fragt man zu oft einer eine Zeitperiode bestimmte Endpunkte ab, bekommt man Fehlermeldungen. Mastodon.py hat in diesem Fall verschiedene Strategien, wie es damit umgeht. Dazu aber in einem späteren Beitrag.
(DIR) Post #AJ4aHOrsi3K036tY4u by beandev@write.tchncs.de
2022-05-03T10:57:57.995113Z
0 likes, 0 repeats
EinführungEs gab vor ein paar Tagen die Frage, ob man Timelines anderer Instanzen lesen kann, ohne einen Account zu haben. Oder anders ausgedrückt: Kann man einer kompletten Mastodon-Instanz folgen?Die Antwort ist Ja und Nein. Ja, es ist technisch möglich. Nein, mir ist kein Client bekannt, der das unterstützt (wenn wer einen Client kennt, dann würde ich mich über eine Info freuen).Wenn man in die REST API von Mastodon schaut, wird man nicht sofort sicher sein, ob sowas möglich ist. Aber es gibt ein paar Hinweise in dem API-Guide, dass einige Endpunkte keine User-Authentifizierung benötigen und einige Endpunkte komplett öffentlich sind. Damit haben wir alles zusammen, dass es eine technische Lösung gibt (unabhängig davon, dass man theoretisch einen ActivityPub Server laufen lässt, um direkt auf die Sync-Streams zuzugreifen).Weiterhin nutze ich die Mastodon.py Bibliothek, damit sich im Hintergrund um alles gekümmert wird. Abruf der lokalen TimelineDie lokale Timeline sind bekanntermaßen die Nachrichten (Beiträge, Tröts, Toots, Status-Nachrichten), die alle Mitglieder der Instanz öffentlich gelistet haben. Es erfordert, wie schon gesagt, keine Authentifizierung eines lokalen Accounts (und damit auch kein Account) auf der Instanz, diese Timeline zu lesen.Somit ist das Script ziemlich simpel:import jsonfrom mastodon import Mastodonmastodon = Mastodon ( api_base_url='https://bildung.social')local = mastodon.timeline_local()print (json.dumps(local, indent=4, default=str))Die Methode timeline_local(), tut das, was man erwartet hätte. Es liest die letzten 20 Nachrichten der lokalen Timeline der Instanz aus.
(DIR) Post #AJ4nR4UOdBX794qscq by beandev@write.tchncs.de
2022-05-03T13:25:23.402075Z
0 likes, 0 repeats
Im letzten Artikel Mastodon Instanz Local Timeline lesen, ohne Anmeldung wurde davon ausgegangen, dass die Lokale Timeline (auch public timeline genannt), öffentlich einsehbar ist. Allerdings ist es möglich, dass Admins der Instanz das deaktiviert haben. Die API Dokumentation schreibt dazu:OAuth: Public. Requires app token + read:statuses if the instance has disabled public preview.Dies ist meiner Meinung nach ein Fehler in der Dokumentation. Wenn man nämlich in den git commit schaut, sieht man diese Sourcecode Änderung:class Api::V1::Timelines::PublicController < Api::BaseController before_action :require_user!, only: [:show], if: :require_auth?require_auth ist nur ein Check der Konfiguration, aber require_user macht klar, dass ein App Token nicht reicht, man muss mindestens als User angemeldet sein.Allerdings ist ein abgeschaltetes Preview extrem selten und evtl. kann man davon ausgehen, dass auch weitere Zugriffe auf die API von den Admins nicht erlaubt ist.Ob eine Instanz überhaupt das anbietet, kann man schnell so ermitteln:Auf www.unmung.com/mastoview gehen (Achtung, das UI ist aus dem letzten Jahrtausend)In das Feld oben links den Instanznamen eingeben (ohne Protokoll)Local auswählen und dann Preview anklicken.Dann sollte was zu sehen sein.Ich habe noch über instances.social/list gesucht und nur eine einzige Instanz mit UP gefunden, die Local Timeline Preview deaktiviert hatte. In dem Fall gibt es die Fehlermeldung: File "C:\DevTools\Core\Python39\lib\site-packages\decorator.py", line 232, in fun return caller(func, *(extras + args), **kw) File "C:\DevTools\Core\Python39\lib\site-packages\mastodon\Mastodon.py", line 102, in wrapper return function(self, *args, **kwargs) File "C:\DevTools\Core\Python39\lib\site-packages\mastodon\Mastodon.py", line 753, in timeline_local return self.timeline('local', max_id=max_id, min_id=min_id, File "C:\DevTools\Core\Python39\lib\site-packages\decorator.py", line 232, in fun return caller(func, *(extras + args), **kw) File "C:\DevTools\Core\Python39\lib\site-packages\mastodon\Mastodon.py", line 102, in wrapper return function(self, *args, **kwargs) File "C:\DevTools\Core\Python39\lib\site-packages\mastodon\Mastodon.py", line 734, in timeline return self.__api_request('GET', url, params) File "C:\DevTools\Core\Python39\lib\site-packages\mastodon\Mastodon.py", line 3425, in __api_request raise ex_type(mastodon.Mastodon.MastodonAPIError: ('Mastodon API returned error', 422, 'Unprocessable Entity', 'This method requires an authenticated user')Und die Exception macht auch klar, dass ein User (Account) mit Anmeldung notwendig ist.
(DIR) Post #AJ6IFQ39w0EkHDznGK by beandev@write.tchncs.de
2022-05-04T06:45:21.496896207+00:00
0 likes, 0 repeats
Den Fehler in der Dokumentation habe ich mal gemeldet, evtl. fixe ich das selber per PR: https://github.com/mastodon/documentation/issues/942 #mastodon #documentation #api
(DIR) Post #AJ6Mj5b4wTDlLbGSfo by beandev@write.tchncs.de
2022-05-04T07:35:32.732617Z
0 likes, 0 repeats
Dieser Blog beschäftigt sich mit OpenSearch (des OpenSource Forks zu ElasticSearch) und wie man einen Cluster mit Python anspricht. Meine primäre Sprache, in der ich groß geworden bin, ist zwar Java, aber Python eignet sich für einige Aufgaben besser.Viele Beispiele werden sich auf das Fediverse und die dort vorhandene Daten, speziell auch auf Mastodon, beziehen.Dieser Blog ist als Ergänzung zu den vielen anderen Informationsquellen gedacht und verweist regelmäßig auf andere Webseiten und Blogs. Die nächsten Themen, die auf meiner Todo-Liste stehen:OpenSearch in Docker (für die Entwicklung und Spielereien)Document Index Schema Definitionen Speichern von Status-Nachrichten (Mastodon) oder Blog-Artikel aus dem FediverseTipps zur Suche und Analyse
(DIR) Post #AJ6p2fCPazsVz4qgq0 by beandev@write.tchncs.de
2022-05-04T12:52:31.709254Z
0 likes, 0 repeats
EinführungIm Blog Artikel zu den ersten Schritten, wurde umfangreich erklärt, wie man eine App registrieren kann. Genutzt wurde das für das erste Beispiel zum Lesen einer lokalen Timeline aber gar nicht. Das liegt daran, dass Mastodon drei verschiedene Ebenen der Authentifizierungen von Identitäten nutzt. Public bedeutet, man braucht gar keine Authentifizierung, App Code wird es in der Mastodon API genannt, wenn man mindestens mit einer Application registriert ist und sich einen Application Code geholt hat, mit dem man auf die API zugreifen will und letztendlich User, wo man als Identität einen Account benötigt, uns man mit diesem sich authentifiziert. Es gibt eigentlich nur einen Endpunkt in der REST API der Mastodon Instanz, die App Code voraussetzt. Das ist dann, wenn man einen neuen Account auf der Instanz anlegen will (was aber auch nur geht, wenn die Registrierung “offen” ist). Nahezu alle anderen Endpunkte sind entweder Public oder erfordern einen User.Der erste TootGehen wir mal davon aus, ein Account wurde auf einer Instanz bereits registriert. Wir haben auf derselben Instanz noch unsere App registriert (wenn nicht, dann hier nachschauen), dann können wir mit wirklich wenigen Zeilen eine Nachricht an das Fediverse schicken.Der erste Teil ist wie erwartet: Unsere App-Registrierungsdaten werden als client_id und client_secret übergeben:from mastodon import Mastodonmastodon = Mastodon ( client_id='ep********************************************Xo', client_secret='AY****************************************0s', api_base_url='https://social.tchncs.de')Nun brauchen wir aber den eigentlich Login des Users, um diese Identität anzunehmen (eigentlich arbeitet nun unser Client Script im Auftrage des Users):mastodon.access_token = mastodon.log_in ( username='<die email-adresse des users>, password='<das password des users>, scopes=['read', 'write'])Username und Password schreibt man üblicherweise nicht in Scripts, aber wir spielen ja noch rum. Besser wäre es, das Script liest sie aus den Argumenten ab oder es wird per Eingabe abgefragt.Wir brauchen den Scope ‘write’ um eine Nachricht zu senden. Das ist unglaublich einfach:response = mastodon.toot ("Das ist ein Test, bitte ignorieren.")print (json.dumps(response, indent=4, default=str))Das schauen wir uns mal in der Timeline an:Perfekt, hat wohl geklappt. Wir nervten gerade ein paar tausend aktive Nutzer in der lokalen Timeline mit unserem Test.Spannend ist auch die Response des toot()-Aufrufs.Hier ein gekürzter Auszug:{ "id": 108243723640515608, "created_at": "2022-05-04 12:42:26.372000+00:00", "visibility": "public", "language": "de", "uri": "https://social.tchncs.de/users/beandev/statuses/108243723640515608", "content": "<p>Das ist ein Test, bitte ignorieren.</p>", "reblog": null, "application": { "name": "Spielplatz", "website": "https://social.tchncs.de/@beandev" }, "account": { "id": 38900, "username": "beandev", # ... }, "media_attachments": [], "mentions": [], "tags": [], "emojis": [], "card": null, "poll": null}Man sieht aber in der Ausgabe, dass das Response-Objekt dramatisch größer ist. Lauter Attribute und vor allem eine komplette Account-Information. Tatsächlich ist Mastodon (und alle Instanzen) so geschwätzig, weil damit die Clients mit nur einer Response alle wichtigen Informationen darstellen können, ohne weitere Roundtrip-Anfragen zu stellen. Das ist in einem föderierten, verteilten System erheblich einfacher, als ständig mit Nachfragen der Clients bombardiert werden zu müssen.Abschließend sei gesagt, dass toot() nur für den ersten Test sinnvoll ist. Es gibt eine erheblich bessere Methode status_post(), die man dazu verwenden sollte (dazu aber mehr in einem anderen Artikel).
(DIR) Post #AJ8amF12AS1n6Niofw by beandev@write.tchncs.de
2022-05-05T09:22:24.057697Z
0 likes, 0 repeats
In der Mastodon-API nennt sich ein Toot tatsächlich Status und so lässt sich gleich auch schneller die notwendigen Informationen zur Struktur und Inhalt eines Toot finden. Ich möchte die Dokumentation hier nicht wiederholen, sondern nur auf ein paar wichtige Bereiche eingehen.Noch mal das Beispiel aus dem letzten Blog-Artikel (gekürzt):{ "id": 108243723640515608, "created_at": "2022-05-04 12:42:26.372000+00:00", "in_reply_to_id": null, "in_reply_to_account_id": null, "sensitive": false, "spoiler_text": "", "visibility": "public", "language": "de", "uri": "https://social.tchncs.de/users/beandev/statuses/108243723640515608", "url": "https://social.tchncs.de/@beandev/108243723640515608", "replies_count": 1, "reblogs_count": 1, "favourites_count": 1,Neben den erwartbaren Attributen wie die ID, Daten der Erstellung, Sichtbarkeitseinstellungen und dem Inhalt der Nachricht selbst, finden sich einiges an Informationen, die erstmal überraschen könnten. Vor allem gibt es schon ein paar statistische Daten, wie häufig der Status favorisiert oder geboostert wurde. Daraus kann man schon mal eine Konsequenz ableiten. Wenn ein Toot über die API zu verschiedenen Zeitpunkten abgefragt wird, erhält man unterschiedliche Responses. Schauen wir uns den Content an: "content": "<p>Das ist ein Test, bitte ignorieren.</p>",Da sind HTML Tags drin! Erinnern wir uns an den Befehl, den wir verwendet hatten:response = mastodon.toot ("Das ist ein Test, bitte ignorieren.")Da muss also was passiert sein. Zudem bedeutet das, es spricht nichts gegen HTML-Auszeichnungen in Nachrichten. Allerdings wird man das in keinem Micro-Blogging Client (für z.B. Mastodon) im Fediverse finden. Das ist eine Beschränkung, die andere Dienste (im Bereich des Publishing und Macro-Blogging) nicht haben (z.B. Friendica). Aber alle diese Dienste kommunizieren miteinander auf dem gleichen Protokoll ActivityPub und somit finden sich auf einmal HTML Tags im Content, die uns die API freundlicherweise angefügt hat. Das geht sogar noch viel weiter, wie ich noch später erläutern werde. Nicht sehr überrascht es, dass man auch die Application als Information geliefert bekommt. Einige Clients zeigen das zu jedem Toot direkt an: "application": { "name": "Spielplatz", "website": "https://social.tchncs.de/@beandev" },Als ich das erste Mal die Response zu den Timeline-Daten und Statusnachrichten gesehen hatte, überraschte mich am meisten, für jeden einzelnen Toot die Account-Informationen zu bekommen: ... "application": { "name": "Playground", "website": "https://social.tchncs.de/@beandev" }, "account": { "id": 38900, "username": "beandev", "acct": "beandev", "display_name": "Aljoscha Rittner (beandev)", "locked": false, "bot": false, ...Das erscheint zunächst extrem kostspielig, so viele (eigentlich redundante) Informationen mitzuliefern, ist aber in der föderierten Struktur des Fediverse begründet. Es soll sichergestellt werden, dass jeder Client alle Daten, ohne weitere Rückfragen, zur Verfügung hat. Das ist konsequent implementiert, soweit es sich um textuelle Informationen handelt, bei Daten (Bilder, Videos, Soundstreams) erhält man trotzdem nur Referenzen. Der Vorteil ist, dass es den entfernten Host (Mastodon Instanz) vor vielen Anfragen zu Detailinformationen bewahrt, die extra Roundtrips benötigen (was eine summierte hohe Latenz bedeutet und den Host zusätzlich belasten würde).Am Ende des Beispiel-Toots sehen wir noch ein paar zusätzliche Attribute: ... "media_attachments": [], "mentions": [], "tags": [], "emojis": [], "card": null, "poll": null}...Die sind für unseren ersten Beispiel-Toot leer. Aber gerade diese Attribute haben eine enorme Bedeutung, zusammen mit dem Content. Um etwas Praxis ins Spiel zu bringen, lesen wir mal einen anderen Status aus:import jsonfrom mastodon import Mastodonmastodon = Mastodon ( api_base_url='https://social.tchncs.de')response = mastodon.status (108247697988744452)print (json.dumps(response, indent=4, default=str))Im Browser sieht die Nachricht so aus:Es gibt einen Hashtag, der als Link anzuklicken ist. Nun ist das nicht eine besondere Fähigkeit des Browser-UI. Nein, dieser Link existiert wirklich in den Daten. Der Ausschnitt aus der Response unseres obigen Aufrufs: "content": "<p>Was will die neue <a href=\"https://social.tchncs.de/tags/LucaApp\" class=\"mention hashtag\" rel=\"tag\">#<span>LucaApp</span></a> nicht l\u00f6sen, was die alte schon nicht konnte?</p>"Da wurde also aus einer einfachen Textzeichenkette mit einer ‘#’ Auszeichnung gleich ein ordentliches Stück HTML.Nun der Blick ans Ende der JSON Struktur: ... "media_attachments": [], "mentions": [], "tags": [ { "name": "lucaapp", "url": "https://social.tchncs.de/tags/lucaapp" } ], "emojis": [], "card": null, "poll": null}Dort ist der Hashtag-Ausdruck als tag aufgelistet. Der Name des tag ist in den Daten nur in Kleinbuchstaben beschrieben und dazu gibt es noch ein URL-Link zur Hashtag-Timeline der Mastodon-Instanz.Die Mastodon-Instanz mit ihrer API betreibt also einen großen Aufwand, um aus den neuen Toots bestimmte Informationen zu parsen, diese in HTML umzuwandeln und zugleich diese geparsten Elemente strukturiert zur Verfügung zu stellen. Das wird auch mit @ annotierte Erwähnungen gemacht, den Dateien (wie Bilder und Videos) und den auf Mastodon-Instanzen individuellen Emoticons.
(DIR) Post #AJGtjRmBJraXtXiOIq by beandev@write.tchncs.de
2022-05-09T09:32:28.763157470+00:00
0 likes, 0 repeats
Hey, noch ein aktiver Plume Account auf tchncs :-) - cool!
(DIR) Post #AJgGIQW13PRQ40EjDM by beandev@write.tchncs.de
2022-05-21T15:13:05.205052008+00:00
0 likes, 0 repeats
@pallenberg@mastodon.social \o/ - danke für Dein Feedback.
(DIR) Post #AJgGNBywW4rcR92Ul6 by beandev@write.tchncs.de
2022-05-21T15:13:56.624575586+00:00
0 likes, 0 repeats
@lk108@social.tchncs.de Ich habe das mal korrigiert und hoffentlich verständlich beschrieben :-)
(DIR) Post #AKVWE5qzKHCGSfQgfg by beandev@write.tchncs.de
2022-06-15T08:40:41.589172Z
0 likes, 0 repeats
Für einen Test auf der Mastodon-Instanz, auf der ich beheimatet bin, musste ich Toots über einen bestimmten Zeitraum erstellen. Das wollte ich nicht mit meinem regulären Account machen, also habe ich einen neuen Account angelegt (und den als Bot gekennzeichnet). Die Sichtbarkeit der Toots war nicht wichtig (daher habe ich die nicht gelistet) und Follower waren auch nicht wichtig.Nun war aber der MastodonHelper zwar grundsätzlich mal dafür angedacht mehr als einen Account nutzen zu können, aber es fehlten noch die passenden Implementationen.Das habe ich mit dem letzten Commit erledigt.In der mastobot-config.yaml kann man nun unter dem Abschnitt instances nicht nur default konfigurieren, sondern mehrere Abschnitte hinterlegen.Das kann so aussehen:instances: default: url: https://social.tchncs.de username: ~ login: true mybot_on_tchncs: url: https://social.tchncs.de username: mybot@myprovider.de login: trueDie Default Konfig bleibt, wie ihr es eingerichtet habt. Dazu können beliebig weitere Instanzen mit Usern hinzugefügt werden.Das muss man aber dann auch explizit im Python Script dann so aufrufen. Dazu gibt es den neuen Parameter instance_id (der mit default vorbelegt ist):mastodon = mastobot.MastodonHelper.open_or_create_app( config_yaml='mastobot-config.yaml', instance_id="mybot_on_tchncs", login=True)Da login=True gesetzt ist, wird ein Account-Login über den Username gefordert. Ihr meldet euch im Terminal also einmal an. Der Access-Token eurer Anmeldung wird dann im home-Ordner gespeichert. Das bedeutet, bei weiteren Aufrufen des Script gibt es keine Interaktion mehr: das Script meldet sich automatisch mit dem Access-Token an.Ich musste dafür auch die Dateinamen der Dateien ändern, die die secrets und tokens speichern. Das passt aber weiter zu dem bisherigen Verhalten von dem MastodonHelper.Man kann also weiterhin auch den einfachen Aufruf nutzen (was die default-Konfiguration nutzt):mastodon = mastobot.MastodonHelper.open_or_create_app( config_yaml='mastobot-config.yaml', login=True)
(DIR) Post #ALeYtUbaDCQv8irKca by beandev@write.tchncs.de
2022-07-19T15:12:46.698541Z
0 likes, 0 repeats
EinleitungDieser Blogartikel ist dafür gedacht, eine fortlaufende Liste an Crates zu beschreiben, die Zugriffe auf Persistenzen erlauben. Die Liste wird über die Zeit aktualisiert.TreiberPostgreSQLPostgres ist eine sehr beliebte RDBMS Datenbank.Postgres (postgres)Rust docs: https://docs.rs/postgres/latest/postgres/Repository: https://github.com/sfackler/rust-postgresAsync: Nein, SSL/TLS: Ja, Native: JaTokio PostgreSQL (tokio_postgres)Rust docs: https://docs.rs/tokio-postgres/latest/tokio_postgres/Repository: https://github.com/sfackler/rust-postgresAsync: Ja (pipelining), SSL/TLS: Ja, Native: JaMySQLMySQL ist eine sehr beliebte RDBMS Datenbank und die erste Wahl für die meisten Anwendungen, die eine SQL-Datenbank verwenden. MySQL (mysql)Rust docs: https://docs.rs/mysql/latest/mysql/Repository: https://github.com/blackbeam/rust-mysql-simpleAsync: Nein, SSL/TLS: Ja (via nativetls oder rustls), Native: JaMySQL Async (mysql_async)Rust docs: https://docs.rs/mysql_async/latest/mysql_async/Repository: https://github.com/blackbeam/mysql_asyncAsync: Ja, SSL/TLS: Ja, Native: JaSQLiteSQLite ist eine prozessinterne Datenbank mit SQL-Unterstützung, die von mehreren Plattformen für die Verwaltung von Datenspeichern verwendet wird.Rust sqlite (rusqlite)Rust docs: https://docs.rs/rusqlite/latest/rusqlite/Repository: https://github.com/rusqlite/rusqliteAsync: Nein, SSL/TLS: Nein, Native: NeinMongoDBMongoDB (mogodb)Rust docs: https://docs.rs/mongodb/latest/mongodb/Repository: https://github.com/mongodb/mongo-rust-driverAsync: Ja (tokio oder async-std), SSL/TLS: Ja (openssl), Native: JaMS SQLDer Microsoft SQL Server ist ein relationales Datenbankmanagementsystem von Microsoft. Microsoft SQL Server. Tiberius TDS Client (tiberius)Rust docs: https://docs.rs/tiberius/latest/tiberius/Repository: https://github.com/prisma/tiberiusAsync: Ja (tokio, async-std, smol), SSL/TLS: Ja (native-tls, rustls, openssl), Native: JaOracleOracle Database (auch Oracle Database Server, Oracle RDBMS) ist eine Datenbankmanagementsystem-Software des Unternehmens Oracle.Sibyl (sibyl)Rust docs: https://docs.rs/sibyl/latest/sibyl/Dokumentation: https://quietboil.github.io/sibyl/Repository: https://github.com/quietboil/sibylAsync: Ja (tokio, async-std, actix), SSL/TLS: Nein, Native: JaRedisRedis ist eine In-Memory-Datenbank, die für die Erstellung von Caches, Worker Queues und Microservices verwendet werden kann. Das Redis-Crate bietet sowohl High-Level- als auch Low-Level-APIs. Alle Abfragen sind pipelined, was bedeutet, dass mehrere Abfragen gleichzeitig gesendet werden können.Redis-rs (redis)Rust docs: https://docs.rs/redis/latest/redis/Repository: https://github.com/redis-rs/redis-rsAsync: Ja (via tokio oder async-std, unterstützt pipelining), SSL/TLS: Ja, Native: JaLevelDBLevelDB wurde von Google entwickelt und ist ein reiner Key-Value-Speicher. Google LevelDB (leveldb)Rust docs: https://skade.github.io/leveldb/leveldb/Repository: https://github.com/skade/leveldbAsync: Nein, SSL/TLS: Nein, Native: NeinMemCacheMemcached ist ein freies, quelloffenes, hochleistungsfähiges Caching-System für verteilte Speicherobjekte. memcache ist ein in reinem Rust geschriebener Memcached-Client. Er unterstützt mehrere Instanzen von Memcached. Einige Funktionen, einschließlich der automatischen JSON-Serialisierung und Komprimierung, sind noch nicht verfügbar.MemCache (memcache)Rust docs: https://docs.rs/memcache/latest/memcache/Repository: https://github.com/aisk/rust-memcacheAsync: Nein, SSL/TLS: Ja, Native: JaCassandra / ScyllaDBCassandra ist eine verteilte, skalierbare SQL-Datenbank. crdrs ist ein Datenbanktreiber für Cassandra und ScyllaDB.CD-rs (cdrs)Rust docs: https://docs.rs/cdrs/latest/cdrs/Repository: https://github.com/AlexPikalov/cdrsAsync: Nein, SSL/TLS (via nativetls): Ja, Native: JaCD-rs Async (cdrs-async)Rust docs: https://docs.rs/cdrs-async/0.1.0-alpha.0/cdrs_async/Repository: https://github.com/AlexPikalov/cdrs-asyncAsync: Ja (via async-std), SSL/TLS: Ja (via nativetls), Native: JaElasticSearchElasticSearch ist im Prinzip keine Datenbank, sondern ein invertierter Suchindex basierend auf JSON Dokumenten.Elasticsearch-rs (elasticsearch)Rust docs: https://docs.rs/elasticsearch/latest/elasticsearch/Repository: https://github.com/elastic/elasticsearch-rsAsync: Ja (via tokio), SSL/TLS: Ja (via nativetls oder rustls), Native: JaOpenSearchOpenSearch ist ein Fork von ElasticSearch und ist damit auch keine Datenbank, sondern ein invertierter Suchindex basierend auf JSON Dokumenten.Opensearch-rs (opensearch)Rust docs: https://docs.rs/opensearch/latest/opensearch/Repository: https://github.com/opensearch-project/opensearch-rsAsync: Ja (via tokio), SSL/TLS: Ja (via nativetls oder rustls), Native: JaODBCDie Odbc-Schnittstelle (Microsoft Open Database Connectivity) ist eine C-Programmiersprachenschnittstelle, mit der Anwendungen auf Daten aus einer Vielzahl von Datenbankverwaltungssystemen (DATABASE Management Systems, DBMSs) zugreifen können. ODBC ist eine Low-Level-Schnittstelle, die speziell für RDBMS entwickelt wurde.ODBC Treiber greifen also nicht auf eine spezifische Datenbank zu, sondern bieten eine API für ODBC-fähige Datenbanken.ODBC-sys (odbc-sys)Rust docs: https://docs.rs/odbc-sys/latest/odbc_sys/Repository: https://github.com/pacman82/odbc-sysAsync: Nein, SSL/TLS: Nein, Native: Nein (FFI)ODBC-api (odbc-sys) Info: Abstraktions-Schicht zu ODBC-sysRust docs: https://docs.rs/odbc-api/latest/odbc_api/Repository: https://github.com/pacman82/odbc-apiAsync: Nein, SSL/TLS: Nein, Native: Nein (FFI via ODBC-sys)SQL AbstraktionenCornucopia (cornucopia)Cornucopia ist ein kleines CLI-Programm, das auf tokio-postgres aufbaut, um PostgreSQL-Workflows in Rust zu erleichtern.Cornucopia wandelt PostgreSQL-Abfragen in Rust Code um. Jede Abfrage wird gegen das Schema vorbereitet, um sicherzustellen, dass die Statements gültiges SQL sind. Diese Prepared Statements werden dann verwendet, um typ-geprüften Rust-Code für die Abfragen zu erzeugen.Das Framework ist kein OR-Mapper, sondern ein Sourcecode Generator für SQL Statements.Hat man folgendes Schema:CREATE TABLE Author ( Id SERIAL NOT NULL, Name VARCHAR(70) NOT NULL, Country VARCHAR(100) NOT NULL, PRIMARY KEY(Id));Und diese annotierte SQL Anweisung:--! authors()*SELECT * FROM Author;generiert sich folgende Rust Funktion:pub async fn authors<T: GenericClient>(client: &T) -> Result<Vec<(i32, String, String)>, Error> { let stmt = client .prepare("SELECT*FROMAuthor;", ) .await?; let res = client .query_raw(&stmt, std::iter::empty::<i32>()) .await? .map(|res| { res.map(|res| { let return_value_0: i32 = res.get(0); let return_value_1: String = res.get(1); let return_value_2: String = res.get(2); (return_value_0, return_value_1, return_value_2) }) }) .try_collect() .await?; Ok(res)}Damit wird eine Menge Boilerplate Implementierung an den Code-Generator ausgelagert und in der Entwicklung muss man sich nicht mehr damit rumschlagen.BewertungCornucopia fokussiert sich auf die Codegenerierung durch annotierte SQL Statements und macht dies ausschließlich für tokio-postgres. Vorteil ist, dass man keine Makros hat und der generierte Code exakt zum DB Schema passt, Async Tokio von Haus aus beherrscht und Connection Pooling via deadpool-postgress einbindet. Damit ist es nur für ganz bestimmte Lösungen geeignet, erfreut sich aber einer begeisterten Anhängerschaft. FähigkeitenTreiberTokio PostgreSQL (Rust safe)AsyncJa (tokio)TLS/SSLÜber den Tokio PostgreSQL TreiberRessourcenCrate: https://crates.io/crates/cornucopiaRepository: https://github.com/cornucopia-rs/cornucopiaCLI Dokumentation: https://github.com/cornucopia-rs/cornucopia/blob/main/cli.mdSQLxSQLx ist eine SQL Abstraktions-Bibliothek, um sichere SQL Abfragen, unabhängig der darunter liegenden Datenbank zu schreiben. Typischerweise sind die Abfragen laufzeit-dynamisch, da alle Bezeichner als Strings deklariert werden. SQLx überprüft nicht die Syntax der generierten SQL Statements. D.h. es ist durchaus möglich, fehlerhaften SQL mit SQLx zu generieren. Applikationen mit SQLx benötigen deswegen eine optimale Testabdeckung. Zusätzlich hat SQLx eine Statement Verifikation zur Compiler-Zeit. Die Abstraktion ist sehr Low-Level, wie das folgende Beispiel zeigt:#[derive(sqlx::FromRow)]struct User { name: String, id: i64 }let mut stream = sqlx::query_as::<_, User>( "SELECT * FROM users WHERE email = ? OR name = ?") .bind(user_email) .bind(user_name) .fetch(&mut conn);BewertungSQLx ist ein ausgezeichnetes Framework zur flexiblen Erzeugung von SQL Statements, das kaum einschränkt und dazu eine große Treiberunterstützung (sogar mit zwei Rust Treibern) bietet sowie Async und TLS nutzen kann. Jenseits der Verifikation während des Compile-Vorgangs ist man aber auf sich und die eigene Testabdeckung angewiesen. Benötigt man eine zusätzliche Abstraktion, solle man sich SeaQuery oder gar SeaORM anschauen, die beide auf SQLx basieren. FähigkeitenTreiberSQlitePostgreSQL (Rust safe)MySQL (Rust safe)MS SQLAsyncJa (actix, async-std und tokio)Transport Layer SecurityFür Postgres und MySQLRessourcenRepository: https://github.com/launchbadge/sqlxRust Doc: https://docs.rs/sqlx/latest/sqlx/SeaQL / SeaQuerySeaQuery ist ein dynamischer Query Builder, der SQLx nutzt. Damit bietet SeaQuery eine sichere Typisierung, als es SQLx bietet. Die Bezeichner der SQL Elemente werden über Enums mit Traits implementiert, indem die Enums zu den String-Bezeichnern mit pattern matching zugewiesen werden (was über ein derive automatisiert werden kann). Der QueryBuilder arbeitet dann nicht mehr mit Strings, sondern nur mit den Enums, die Anwendern schon zur Compile-Zeit eine Fehlerprüfung auf korrekte Bezeichner bietet. Eine Abfrage sieht dann so aus:assert_eq!( Query::select() .column(Glyph::Id) .from(Glyph::Table) .cond_where( Cond::any() .add( Cond::all() .add(Expr::col(Glyph::Aspect).is_null()) .add(Expr::col(Glyph::Image).is_null()) ) .add( Cond::all() .add(Expr::col(Glyph::Aspect).is_in(vec![3, 4])) .add(Expr::col(Glyph::Image).like("A%")) ) ) .to_string(PostgresQueryBuilder), [ r#"SELECT "id" FROM "glyph""#, r#"WHERE"#, r#"("aspect" IS NULL AND "image" IS NULL)"#, r#"OR"#, r#"("aspect" IN (3, 4) AND "image" LIKE 'A%')"#, ] .join(" "));BewertungWenn man sich nicht mit Schreibfehlern an zig verschiedenen Stellen im Sourecode rumschlagen will, ist SeaQuery eine sehr gute Abstraktion für SQLx. Damit wird die Deklaration der Bezeichner an einem zentralen Punkt der Schema-Definition festgelegt und man umgeht die fehlerträchtigen String Konstanten. Mit dem Iden-derive-Makro wird das Mapping der Bezeichner sogar automatisiert. Ansonsten kann man mit SeaQuery alles machen, was man von SQLx gewohnt ist. Die zusätzliche Indirektion über die Enums ist zwar etwas aufwendiger, aber der Gewinn an korrekten Code ist damit aufgewogen. Aber auch für SeaQuery gilt, dass keinerlei Prüfung gegen echte DB Schemata gemacht wird. D.h., was als Deklaration festgelegt wird, muss nichts mit dem DB-Schema zur Laufzeit zu tun haben. Es erlaubt eine hohe Flexibilität, fordert aber auch weiterhin eine umfangreiche Testabdeckung.FähigkeitenTreibersqlx-mysql (Rust safe)sqlx-postgres (Rust safe)sqlx-sqlitepostgrespostgres-*rusqliteRessourcenRepository: https://github.com/SeaQL/sea-queryDokumentation: https://www.sea-ql.org/SeaORM/docs/index/Object Relational Modeling (ORM)Es gibt nicht viele Multi-DB ORM Crates, aber die wenigen lassen sich gut produktiv einsetzen und sind auch keine Konkurrenz zueinander, sondern bilden ganz eigene Paradigmen ab. Ansonsten gibt es Projekte, wie Sand am Meer, die für spezifische Datenbanken ORM-Funktionalitäten anbieten. Diese habe ich hier aber nicht betrachtet.DieselDiesel ist ein statisches ORM Builder System, das darauf abzielt, schon zur Compiler-Zeit ein vollständiges OR-Mapping durchzuführen. Die Makros sind auf typische Spaltenzahlen für Tabellen konfigurierbar. Wenn man z.B. mehr als 32 Spalten benötigt, muss man die Konfiguration anpassen, was aber auch die Compiler-Zeit verlangsamt. Spalten werden als Structs modelliert und erhalten Traits, um die Predicates zu deklarieren. Abfragen in Diesel bieten eine gute Abstraktion:let versions = Version::belonging_to(krate) .select(id) .order(num.desc()) .limit(5);let downloads = version_downloads .filter(date.gt(now - 90.days())) .filter(version_id.eq(any(versions))) .order(date) .load::<Download>(&conn)?;BewertungMan findet wohl Diesel in fast jedem Softwareprojekt, dass Persistierung in den drei meistgenutzten RDBMS Systemen durchführt. Damit gibt es eine Menge Beispielcode, eine sehr stabile Codebasis und das statische OR Mapping ist zur Laufzeit unschlagbar schnell. Das erkauft man sich mit einigen fehlenden Features (wie beispielsweise Async) und ggf. eine lange Compiler-Zeit.Diesel eignet sich besonders für überschaubare Datenmodelle, die primär mit CRUD Aktionen auskommen und es einfache, normalisierte Relationen gibt. FähigkeitenTreiberPostgreSQLMySQLSQLiteAsyncNein (Experimentell via https://github.com/weiznich/diesel_async - mit tokio postgres)Transport Layer Security (TLS)NeinRessourcenHomepage: https://diesel.rs/Repository: https://github.com/diesel-rs/dieselDokumentation: https://diesel.rs/guides/getting-startedRust-Doc: https://docs.diesel.rs/1.4.x/diesel/index.htmlSeaORMSeaORM ist jünger als Diesel und basiert auf SeaQuery und SQLx. Damit ist SeaORM ein dynamisches ORM System, dass zur Laufzeit Objekt-Relationen und das Mapping generiert. Wenn man mehr Flexibilität benötigt, kann man SeaQuery benutzen, welches eine SQL Abstraktion bietet, die nicht an ein OR Mapping typisiert ist.SeaORM nutzt Enums, um Spalten zu modellieren. SeaORM und SeaQuery können gemeinsam genutzt werden. BewertungMan kann SeaORM nicht wirklich mit Diesel vergleichen, da sie unterschiedliche Ansätze verfolgen. Wenn aber einen schnellen Compilerlauf für sehr große Datenmodelle benötigt und man zudem (eben aufgrund des großen Datenmodells) eine hohe Flexibilität an Abfragemöglichkeiten benötigt, wenn man nicht mit einem statischen Datenmodell arbeiten kann, dann ist SeaORM unschlagbar. Das erkauft man sich mit potenziellen Laufzeitfehlern, die man gut mit Tests abdecken sollte. Auch da bietet SeaORM eine gute Unterstützung. Überdies bietet SeaORM von Haus aus eine hervorragende Dokumentation.SeaORM ist sinnvoll für große Datenbank-Schemata, mit sehr vielen Tabellen und Attributen, die mit komplexen Abfragen verknüpft werden müssen. Ist das ORM noch zu statisch, kann mit SeaQuery jedes erdenkliche SQL Statement gebaut werden. SeaORM ist alternativlos, wenn man mit der gleichen Applikation auf unterschiedlichen Schemata arbeiten will.FähigkeitenTreiberSQlite (via sqlx-sqlite)PostgreSQL (via sqlx-postgres, Rust safe)MySQL (via sqlx-mysql, Rust safe)MS SQL - Unbekannt, SQLx unterstützt es, in der SeaORM Doku wird es nicht erwähntAsyncJa (actix, async-std und tokio)Transport Layer Security (TLS)Für Postgres und MySQL (über SQLx)RessourcenHomepage: https://www.sea-ql.org/SeaORM/Repository: https://github.com/SeaQL/sea-ormDokumentation: https://www.sea-ql.org/SeaORM/docs/index/rustormDas Framework hat sich auf das Data Access Object Mapping von SQL Ergebnissen spezialisiert und unterstützt damit die Übertragung der SQL Resultsets in Structs. Binding von Abfrage-Parameter besteht wohl, aber es gibt keine Beispiele.BewertungDie SQL Abfragen sind sehr Low-Level. Ein Guide oder umfangreiche Dokumentation gibt es nicht, aber die Möglichkeiten sind sehr fokussiert, sodass man sich schnell einarbeiten kann. Transaktionen werden wohl nicht unterstützt. Für größere Projekte ist rustorm eher nicht zu empfehlen. FähigkeitenTreiberSQlitePostgreSQLMySQLRessourcenHomepage: -Repository: https://github.com/ivanceras/rustormRust-Doc: https://docs.rs/rustorm/0.20.0/rustorm/
(DIR) Post #APGoTBECZR2IP1ZWPQ by beandev@write.tchncs.de
2022-11-04T19:44:40.923501Z
0 likes, 0 repeats
@robertriebisch@social.tchncs.deDas wollte ich noch mal in einem separaten Artikel zum Thema machen. Da geht es mehr um Möglichkeiten, wie man, mit organisatorischen Mitteln, privat bleiben und sich auch selbst einen Schutzraum bauen kann (passende Instanz suchen, Blocken, Klarname vs. Anonym usw usf).
(DIR) Post #APbDZKC6xJq6wZRBDM by beandev@write.tchncs.de
2022-11-14T15:58:45.264324Z
1 likes, 1 repeats
EinleitungIch schreibe das auch ein wenig für mich, weil es doch erheblich komplexer ist, als auf anderen Plattformen. Zudem bin ich selbst (zum Beispiel bei ungelisteten Toots) einer Urban Legend aufgesessen. Deswegen gibt es den hier etwas umfassenden Artikel, der beschreibt, wie sich eure Toots verteilen.Wenn ihr komplett neu im Fediverse und Mastodon seid, dann solltet ihr vielleicht zunächst den Artikel “#neuHier - und wie interagiere ich?” anschauen. Ansonsten könnte dieser Artikel etwas zu abstrakt sein. Wer sich aber für die Details interessiert, liest bitte weiter:Verteilung der Toots, das Fediverse und InstanzenGrundlagen der VerteilungIch gehe nicht sehr tief in technische Details ein, aber es ist wichtig zu verstehen, wie das Fediverse funktioniert, in dem sich Mastodon als einer der Dienste bewegt. Das Fediverse beschreibt ein Universum an föderierten Diensten. Föderiert bedeutet, die Dienste kommunizieren untereinander. Universum bedeutet, es gibt nicht nur einen Dienst (wie Mastodon) und es gibt nicht nur einen Server (Instanz). Es gibt inzwischen sehr viele gute Beschreibungen davon, deswegen beschränke ich mich auf den Mechanismus der Verteilung. Es gibt verschiedene Verfahren, wie man Daten zwischen zwei Systemen synchronisieren kann. Dazu gibt auch Verfahren und Methoden, wie man Daten zwischen vielen Systemen austauschen kann. Eine Mastodon-Instanz ist so ein System. Zum Beispiel social.tchncs.de. Den meisten wird eine viel größere Instanz bekannt sein: mastodon.social. Auf beiden Instanzen (und tausenden mehr), sind sehr viele Menschen mit ihren Accounts angemeldet. Diese Menschen leben einen regen Austausch miteinander. Nicht nur auf derselben Instanz, sondern auch über die Instanzen hinweg. Das bedeutet, ich kann mit meinem Account auf social.tchncs.de mit anderen Accounts auf mastodon.social interagieren.Damit das technisch funktioniert, hat man sich für das Fediverse, für ein ganz bestimmtes Verfahren des Datenaustauschs entschieden: das Inbox/Outbox-Verfahren. Damit unterscheidet es sich vom Verfahren nicht sonderlich von E-Mail. Interessanterweise ergeben sich einige Besonderheiten zur Verteilung und Sichtbarkeit von Toots aus der Tatsache, dass das Verteilungssystem darauf aufbaut, dass eure Nachrichten, ähnlich wie E-Mail, von euren Postausgangsfächern zu den Posteingangsfächern anderer Accounts verteilt werden. Dazu später mehr.Das Fediverse nutzt allerdings (und glücklicherweise) nicht das E-Mail-Protokoll und es beschränkt sich nicht auf das Austauschen von Nachrichten zwischen Accounts. Auch die Nachrichten, die ihr verschickt, werden strukturiert (d.h. in maschinenlesbaren, strukturierten Datenpaketen) codiert und (natürlich verschlüsselt) an die Zielsysteme übertragen. Das inzwischen etablierte Protokoll im Fediverse ist ActivityPub. Das müsst ihr euch nicht merken, aber ihr werdet immer mal wieder darauf stoßen und wisst jetzt, wofür es ist.Eure Accountadressen sind nicht zufälligerweise ein Hybrid aus Handles (At-Zeichen mit Namen, wie in vielen sozialen Medien) und Server-Adresse (eurer Instanzadresse, @ instanz-name), beispielsweise @beandev@social.tchncs.de. Denn Server gibt es ja auch wie Sand am Meer. Das ist fast identisch zu E-Mail-Adressen. Eure E-Mail-Adressen beschreiben ebenfalls die Namen der E-Mail-Server (google.com, posteo.de, …). Anhand dieser Accountadressen wird der Austausch der Nachrichten im Fediverse organisiert. Hier endet die Vergleichbarkeit zur E-Mail, weil Instanzen im Fediverse komplexere Regeln haben, was die Verteilung betrifft.Public und Private TimelinesMan unterscheidet zwischen zwei Typen von Timelines. Private und Public Timelines. Die Timelines werdet ihr sicher schon kennen. Hauptsächlich seid ihr in der Home-Timeline (Start, Startseite, Home). Dies ist eure private Timeline. Fast alle anderen Timelines sind öffentliche (public) Timelines, unter anderem die föderierte oder die lokale Timeline (die eurer Heimat-Instanz).Private Timelines sind aus technischer Sicht eine besondere Sicht eurer Eingangs- und Ausgangskörbe auf der eigenen Heimat-Instanz. Die öffentlichen Timelines sind besondere gefilterte Abfragen oder Aggregationen von Nachrichten von eurer und/oder fremder Instanzen. Das hört sich jetzt sehr abstrakt und theoretisch an, ich weiß, aber die folgenden Kapitel beschreiben, was damit gemeint ist. Es ist essenziell für das Verständnis, warum Mastodon so innerhalb des Fediverse funktioniert (und nicht etwas so wie Twitter oder andere Social Media Dienste). Private TimelinesHome TimelineDie Home-Timeline ist euer privater Eingangskorb aller Nachrichten, die das Fediverse nach ein paar Regeln euch zustellt. Die offensichtlichste Regel ist: Ich folge einem Account und lese damit mit, was der Account für Nachrichten schreibt. Ok, das ist jetzt komplett anders als E-Mail (und ich habe es ja oben angekündigt, dass es anders ist). Es ist so ähnlich wie eine Anmeldung an einen Newsletter. Wenn man einem anderen Account folgt, sagt man der Instanz (auf die der Account beheimatet ist), dass man die Nachrichten dieses einen Accounts abonnieren möchte. Im Hintergrund überträgt meine Instanz meine Account-Adresse zur anderen Instanz und richtet ein “Abo” ein. Künftig pusht die andere Instanz die Nachrichten aus der Outbox des von mir gefolgten Accounts in die Inbox meines Accounts. Durch die Technik des Eingangskorbs und der besonderen Aktion des Abonnierens erhaltet ihr erst ab dem Zeitpunkt des Folgens die Nachrichten von dem Account eures Interesses. Wenn ihr an älteren Nachrichten interessiert seid, müsst ihr auf das Profil der Instanz wechseln, um dort die vergangenen Nachrichten zu sehen.Als private Timeline, zeigt die Home-Timeline auch Boosts und Antworten von Accounts an, denen ihr folgt (das kann man für die Home Timeline deaktivieren, aber für public Timelines nicht aktivieren).Direct Message TimelineEinfacher mit E-Mail zu vergleichen, sind “Direct Messages” (DM). Denn das ist im Prinzip ein Push-Verfahren, wie man es von Mail auch kennt. Ich erwähne einen oder mehrere Accounts in dem Text, den ich schreibe und meine Instanz sendet diese Nachrichten an alle Accounts, die im Text aufgelistet sind (ja alle, also passt auf, wenn ihr über andere lästert und den Account-Handle mit in den Text schreibt - aber wir lästern ja nicht auf Mastodon).Es gibt die Möglichkeit DMs in einer eigenständigen Timeline anzuschauen (das geht z.B. im erweiterten Browser UI von Mastodon und einige Smartphone-Apps unterstützen das auch), aber das ist nur eine gefilterte Ansicht der Direktnachrichten aus eurer Home-Timeline. Ihr werdet auch schon bemerkt haben, dass DM tatsächlich ganz normal in eurer Home-Timeline auftauchen (und das Umschlag-Symbol wird gerne mal übersehen).Die DM Timeline ist also eine Schimäre unter den Timelines. Sie ist eher eine Suche auf das Kriterium “Empfangene Nachrichten vom Typ Direct Messages”.Da DMs sich wie normale Nachrichten verhalten (also auch Antworten in Unterhaltungen sein können, die vorab öffentlich waren) und sogar mehreren erwähnten Accounts zugestellt werden, gibt es eine sehr fundamentale Sache, die man wissen sollte: Diese DMs sind, wie alle anderen öffentlichen Nachrichten in meinem Eingangskorb, nicht verschlüsselt. List-TimelinesMan kann sich Listen von Accounts zusammenstellen, denen man folgt. Diese List-Timelines sind nur für mich sichtbar und sammeln die Nachrichten der Accounts in dieser Liste in einem gesonderten Eingangskorb, es handelt sich damit um eine private Timeline. Stecke ich einen Account neu in die Liste, erhalte ich erst ab diesem Zeitpunkt die Nachrichten des Accounts in diese Liste. Nehme ich einen Account aus der Liste heraus, bleiben die Nachrichten (die bisher von dem Account kamen) in der List-Timeline, aber es kommen keine weiteren von diesem Account hinzu. Um es mal technisch (vereinfacht) herunterzubrechen: Eine List-Timeline ist wie eine E-Mail-Client-Regel, die festlegt, dass aus dem Eingangskorb die Nachrichten von bestimmten Accounts in einen separaten Ordner (meine Liste) zu kopieren sind. Public TimelinesLocal TimelineDie lokale Timeline ist die öffentliche Zeitleiste eurer Instanz. Es ist die Zusammenfassung aller öffentlichen Nachrichten von allen Accounts, die auf derselben Instanz beheimatet sind. In einem späteren Kapitel kommen wir zu der Sichtbarkeit von Nachrichten, womit ihr manuell festlegen könnt, ob eure Nachrichten dort erscheinen. Allerdings gibt es auch eine technische Besonderheit: Es werden keine Antworten (Replies) auf andere Toots (auch wenn sie öffentlich sind) und keine geteilten Nachrichten (Boosts), in der lokalen Timeline angezeigt. Antworten werdet ihr nur sehen, wenn ihr eine einzelne Nachricht auswählt, um die Unterhaltung zu sehen. Boosts seht ihr nur in der Home-Timeline von Accounts, denen ihr folgt.Da die Nachrichten der lokalen Timeline (was eigentlich eine Zusammenfassung aller öffentlichen Nachrichten der Outboxen aller Accounts eurer Instanz sind) ohnehin immer auf eurem Heimat-Server liegen, kann man beliebig weit in die Vergangenheit gehen und sich die Nachrichten anschauen. Auch wenn ihr ganz neu auf einer Instanz seid, habt ihr Zugriff auf alles, was mal geschrieben und öffentlich gepostet (und nicht wieder gelöscht) wurde.Federated TimelineIn der föderierten Timeline findet ihr alle öffentlichen Nachrichten eurer beheimateten Instanz und aller verbundenen (Peer) Instanzen. D.h., es sind nicht die Nachrichten aller Instanzen des gesamten Fediverse drin, sondern nur die, zwischen denen es einen Austausch gibt. Die Regel, welche Instanzen im Austausch miteinander sind, kann auf eine grundlegende Sache reduziert werden: folgen sich Accounts auf von zwei unterschiedlichen Instanzen, dann synchronisieren sich die Instanzen und zeigen in ihrer föderierten Timeline alle Local-Timeline Posts der jeweils anderen Instanz mit an. Die Nachrichten in der föderierten Timeline sind die lokalen Nachrichten und dazu die Kopien von Nachrichten der verbundenen Instanzen und können nur soweit zurückverfolgt werden, seitdem sich die eigene und die entfernte Instanz verknüpft haben. Das ist für die Alltagserfahrung irrelevant, weil es so viele Nachrichten darin gibt, dass man das kaum bemerken wird.Hashtag TimelineWenn man neu im Fediverse und bei Mastodon ist, wird man nicht gleich mitbekommen, dass man Hashtags als Timelines haben kann. Diese Hashtag-Timelines sind auch öffentliche Timelines und verhalten sich wie eine gespeicherte Suche. Nach der Suche eines Hashtags hat man eine Timeline der Ergebnisse. Allerdings kann man diese Timeline “anheften”. Im einfachen UI gibt es dann einen Link, im erweiterten UI erscheint eine neue Spalte mit den Nachrichten, die das Hashtag verwenden. Allerdings unterscheiden sich Hashtag-Timelines in der Funktion, von den anderen Timelines. Sie bilden ein Suchergebnis ab. Das beinhaltet auch vergangene Nachrichten und man kann die Timeline weit in die Vergangenheit anschauen. Die beinhalteten Nachrichten sind aber nur aus den Instanzen, mit denen eure Heimat-Instanz föderiert. Eine weitere Besonderheit ist: Es werden auch Antworten aus Unterhaltungen angezeigt (soweit diese öffentlich sind) und die Antworten das Hashtag enthalten. Profile TimelineDie Timeline eines Profils wird häufig nicht als solche wahrgenommen. Es ist nicht die Home-Timeline des Profils, sondern eine teil-öffentliche Ansicht der Nachrichten eines bestimmten Accounts (und anderer Accounts, wenn deren Nachrichten auf dem Profil geboostet wurde). Im Profil sieht man grundsätzlich öffentliche und nicht gelistete Nachrichten. Ist man angemeldet, sieht man noch die Nur Follower Nachrichten, wenn man den Account tatsächlich folgt. Auf dem Profil gibt es zwei Ansichten: Beiträge und Beiträge mit Antworten. Die zweite Sicht zeigt einfach auch zusätzlich öffentliche Antworten an. Im Prinzip ist es auch eine gefilterte Ansicht von Nachrichten für einen Account. Das besondere ist, dass das die aktuellste Sicht ist, die man für die Nachrichten des Accounts haben kann. Alle Statistiken der Nachrichten dort in der Ansicht und auch die Statistiken des Account (Beiträge, Folgt, Folgende) sind der aktuelle Stand auf der Heimatinstanz des Profils. Das kann abweichen von den Informationen, die man in den Timelines der eigenen Instanz stehen (weil es dazu keine aktuelle Synchronisation gab). Sichtbarkeit von TootsIch habe bereits zwei Artikel zu Mastodon geschrieben, wo ich auf die Sichtbarkeit von Toots einging und hier möchte ich noch einmal eingehender beschreiben, was das bedeutet. Zudem gilt es ein paar Missverständnisse aus dem Weg zu räumen, was die unterschiedlichen Sichtbarkeitseinstellungen bedeuten.Grundsätzlich lässt sich jeder Toot manuell auf eine Sichtbarkeit einstellen. Erst mal versendet, ist es nicht mehr möglich, diese zu ändern. Auch nicht mit der neuen Bearbeiten-Funktion. Man kann auch in seinen Einstellungen festlegen, dass man grundsätzlich nicht öffentlich Toots posten will. Man kann dann trotzdem jederzeit einzelne Toots auf “öffentlich” setzen, bevor ich ihn poste.Folgende Sichtbarkeits-Einstellungen gibt es:ÖffentlichNur FollowerNicht gelistetNur Leute, die ich erwähneJe nach eingestellter Sichtbarkeit, ist eine Nachricht unter dieser Einstellung, nur in bestimmten Timelines oder bestimmten Accounts zugänglich. Überdies entscheidet auch der Typ eines Toots (Nachricht, Antwort/Reply, Geteilt/Boost), in welchen Timelines die Nachricht sichtbar wird. Außerdem kann die Kombination des Typs, die Erwähnungen in der Nachricht und die Gefolgschaft beeinflussen, was ich letztendlich sehen kann oder mir verborgen bleibt.Das sind ganz schön viele Parameter. Einen Teil habe ich oben schon in den Kapiteln zu den Timelines abgefrühstückt. Hier versuche ich Licht in das restliche Dunkel zu bringen.Öffentliche NachrichtenSende ich eine neue Nachricht (die keine Antwort und kein Boost ist) mit der Sichtbarkeit “Öffentlich”, dann wird diese zunächst für alle sichtbar, die auf derselben Instanz sind und in die Local Timeline schauen. Ebenso sehen das auch alle meine Follower*innen. Föderiert meine Instanz mit anderen Instanzen, ist die Nachricht auch in dieser Timeline zu sehen, auch auf den anderen verbundenen Instanzen.Antworte ich öffentlich oder booste ich von einem anderen Account eine Nachricht, verändert sich die tatsächliche Sichtbarkeit. Diese Interaktion wird weder in der lokalen, noch in der föderierten Timeline sichtbar. Aber alle, die mir folgen, können das in ihrer Home-Timeline sehen. Das kann zwar jeder für sich individuell abschalten (also ausblenden), aber zugestellt werden Antworten und Boosts immer. Wähle ich in der lokalen oder föderierten Timeline eine Nachricht aus (und wechsele damit zur Unterhaltungs-/Thread-Ansicht), sehe ich die öffentlichen Antworten. Wähle ich in meiner Home-Timeline die öffentliche Antwort (oder einen Boost) einer meiner Follower aus, sehe ich ebenfalls die Unterhaltung (ggf. aber nicht immer vollständig, was dem geschuldet ist, dass jede Nachricht eine individuelle Sichtbarkeit haben kann - auch in Unterhaltungen).Phew, das ist schon ziemlich kompliziert, obwohl man denken könnte, öffentliche Nachrichten sind eben überall für jede*n verfügbar. Nur FollowerFast einfacher als “Öffentliche Nachrichten” ist die Sichtbarkeitseinstellung “Nur Follower”. Jeder Account, der euch folgt und wenn die Person hinter dem Account angemeldet ist, kann diese Nachrichten lesen. Diese Nachrichten werden mit einem geschlossenen Vorhängeschloss gekennzeichnet und macht mit dem Symbol deutlich, dass eine Anmeldung zwingend erforderlich ist. Was bedeutet das für die Timelines? Wenn ich einem anderen Account folge, sehe ich alle Nachrichten des Accounts, die mit der Sichtbarkeitseinstellung “Nur Follower” gesetzt sind. Wenn ich auf die Profilseite des Accounts gehe, dann übrigens auch. Melde ich mich ab, sehe ja meine Home-Timeline ohnehin nicht, wechsele ich nun (abgemeldet) zum Profil des Accounts, das auch “Nur Follower” Nachrichten verschickt, sehe ich diese Nachrichten dann nicht mehr (aber die öffentlichen Nachrichten!). Mastodon filtert hier also “aktiv”, die Nachrichten sind hinter einer Anmelde-Barriere. Aber es gibt eine andere Konsequenz, die fast wichtiger ist: Wenn ein Account seine Privatsphäre wahren will, in dem es “Nur Follower” Nachrichten sendet, kann ich als Account einfach nur dem Account folgen und sehe dann die Nachrichten. Ups. Also muss ein Account sich mit einem “Schlossaccount” vor diesem Automatismus zusätzlich schützen und den Folgewunsch nach Abwägung freigeben. Durch den aktiven Abgleich: “Folge ich und bin ich angemeldet”, gibt es aber eine weitere Konsequenz: Lasse ich zu, dass jemand mir folgt, sieht der Follower dann auch alle alten Nachrichten. Mastodon unterscheidet nicht, ab wann ein Follower dazukommt. Folgt er mir, sieht er alle Nachrichten, die eben für die Folgenden freigegeben wurden. Es ist fast klar, dass die Toots mit der Sichtbarkeit “Nur Follower” auch nicht in der lokalen Timeline und auch nicht in der föderierten Timeline erscheinen. Nicht gelistetDas absolute Mysterium des Fediverse und Mastodon: Ungelistete Nachrichten. Es gibt einige urbane Legenden dazu und es ist nicht wirklich einfach zu erklären, wofür man diesen Typ an Sichtbarkeit benötigt. Die Dokumentation von Mastodon sagt, dass Nicht gelistete Nachrichten sich wie öffentliche Nachrichten verhalten, aber nicht in der lokalen oder föderierten Timeline auftauchen. Ja, so habe ich auch geschaut. Ok. Zunächst, wo die Nachrichten nicht mehr zu sehen sind. Das ist übrigens völlig unabhängig vom Typ: Man sieht die Nachricht nicht in der lokalen Timeline (wenn der Account auf meiner Instanz ist) und ich sehe es nicht in der föderierten Timeline (die ja nur eine Aggregation aller verbundenen lokalen Timelines sind). Also wo ist es dann zu sehen?In meiner Home-Timeline. Und zwar unter allen Bedingungen, die auch öffentliche Nachrichten betreffen: Antworten, Boosts und neue Nachrichten von allen meinen gefolgten Accounts. Ja, das ist so richtig und räumt mit einer Legende auf: Es ist falsch, dass man ungelistete Antworten in Threads nicht sehen kann. Schreibt ein Account, dem ich folge, einen Thread und setzt die 2. bis n. Nachricht auf “Nicht gelistet”, sehe ich bedauerlicherweise all diese Nachrichten trotzdem einzeln in meiner Timeline. Ich sehe sogar Nachrichten vom Typ “Nicht gelistet” in meiner Home-Timeline, die von einem gefolgten Account geboostet wurden. Sogar dann, wenn die Ursprungsnachricht von einem Schlossaccount “ungelistet” geschrieben wurde.Ok, jetzt könnte man sich langsam fragen, wofür das Ganze nun, wenn es sich eigentlich nicht von der Sichtbarkeitseinstellung Nur Follower unterscheidet? Die Nachrichten mit dem Typ “Nicht gelistet” tauchen in keiner Hashtag-Timeline auf (obwohl ja öffentlich). D.h. man kann sich dann Hashtags sparen. “Nicht gelistet” Nachrichten, kann man allerdings auch sehen, wenn man in Mastodon nicht angemeldet ist (also man kann den Link an andere schicken, die nicht im Fediverse oder bei Mastodon sind). Man spricht in diesem Fall davon, dass der Permalink der Nachricht öffentlich ist - es gibt keine Anmelde-Barriere. Das gilt auch für öffentliche Profil-Ansichten, unter der ihr eine Timeline des Profils sehen könnt. Dort seht ihr neben den öffentlichen Nachrichten und Antworten auch die “ungelisteten” Nachrichten. Geht ihr zum Beispiel auf mein Profil und scrollt euch durch die Beiträge, werdet ihr neben den Nachrichten mit der Weltkugel auch Nachrichten sehen, die ein geöffnetes Schloss haben. Das auch für Boosts und Antworten. Das ist der abgrenzende Unterschied zu “Nur Follower”-Nachrichten. Mehr aber auch nicht. Für Schlossaccounts (also Accounts, die Follower nur mit Anfrage zulassen), gilt ebenfalls zu beachten: Auf eurer Profil-Seite werden alle Nachrichten vom Typ “Öffentlich” und “Nur gelistet” angezeigt. Wenn ihr das nicht wollt, müsst ihr als Sichtbarkeit bei “Nur Follower” bleiben. Dann müssen Interessenten an euren Nachrichten angemeldet und eure Follower sein, damit man die Nachrichten gesehen werden können. Also noch mal zusammengefasst: “Nicht gelistet” bedeutet, die Nachrichten sind in den Home-Timelines, nicht in der lokalen und föderierten Timeline, aber grundsätzlich öffentlich zugänglich. Sowohl als Permalinks und auch in jeder Profil-Ansicht, wo die Nachrichten erwähnt oder gepostet wurden, egal ob ihr ein öffentliches oder geschlossenes Profil habt.Nur Leute, die ich erwähneDirect Messages werden Nachrichten genannt, die diese Sichtbarkeit erhalten. Man kann das naturgemäß nur bei neuen Nachrichten setzen oder bei Antworten. Boosts kann man nicht als DM verschicken.Wo kommen die DMs an und wie sichtbar sind diese? Zunächst gilt für DM im Prinzip das Gleiche, wie für “Nur Follower”. DMs sind hinter einer Anmelde-Schranke. Ist man angemeldet, kann man eine Direktnachricht aber nur sehen, wenn man in der DM erwähnt wurde, also wenn die vollständige Fediverse-Adresse im Text (das gilt für Mastodon) geschrieben wurde. Das dürfen auch mehrere Adressen sein. Dann ist es eine “Gruppennachricht”. Alle erwähnten Accounts (Mentions) bekommen die DM! Die Nachricht erscheint dann in deren Home-Timelines mit dem Symbol eines Briefumschlags. Das Mastodon-Browser UI bietet noch eine Timeline, die DMs (die ich empfangen habe) extra filtert. Im erweiterten UI kann ich die Timeline auch anpinnen. Fraglos, dass man DMs nie in der lokalen, föderierten oder in der Profil-Ansicht sehen wird. Direct Messages werden ansonsten wie alle anderen Nachrichten behandelt. Man kann in zunächst öffentlichen Unterhaltung, auf DM wechseln (damit man in kleinerem Kreis weiterdiskutiert), aber die DM (und die Antworten darauf) bleiben in der Unterhaltung “eingehängt”. Mastodon speichert Direct Messages auch nicht verschlüsselt ab. Man kann nachträglich Mentions aus einer DM herausnehmen oder hinzufügen. Man sollte aber beachten, dass bei Unterhaltungen die älteren Antworten dann für neue Teilnehmer nicht sichtbar werden. EpilogDer Verteilmechanismus von Nachrichten im Fediverse und durch Mastodon implementiert und mit ActivityPub ausgeführt, ist sehr komplex und hängt von vielen Parametern ab. Es gibt mehrere Sichtbarkeits-Einstellungen, es gibt die Wahl, wem ich diese Nachrichten zukommen lassen will, es hängt davon ab, ob ich auf derselben Instanz oder auf einer entfernten Instanz bin, ob ich folge oder nicht und in welche Timelines ich schaue. Alle diese Parameter haben ihr Gründe und sind über die Jahre gewachsen. Sie machen das Fediverse sicher nicht unmittelbar intuitiv benutzbar, aber wurden über die Jahre als sinnvoll erachtet. Wenn man sich ein wenig damit auseinandersetzt, wird das irgendwann in Fleisch und Blut übergehen. Ich habe bewusst auf tiefere technische Details verzichtet und hoffe für Anweder*innen ist es verständlich genug. Wenn es trotzdem Verständnisschwierigkeiten gibt, einfach fragen. Ich werde über die Zeit den Artikel auch aktualisieren, wenn notwendig.
(DIR) Post #APbE5ObM6bGdsYXPZA by beandev@write.tchncs.de
2022-11-14T16:05:50.985389Z
0 likes, 0 repeats
@gilde@digitalcourage.socialDas freut mich. Ich habe inzwischen einen weiteren Artikel geschrieben, der mehr ins Detail geht. Ich habe einen Link am Anfang dieses Artikels hinzugefügt.