Nitter krijgt een stakingsbrief van X, terwijl atproto het internet opener maakt

In 2007 schreef Tim Berners-Lee het essay The Giant Global Graph. Hij stelde daarin: "Er zijn kreten uit het hart... voor mijn vriendschap, die relatie tot een ander persoon, om documenten en sites te overstijgen... Dan kan elke andere site of elk ander programma die informatie gebruiken."

Nu X stakingsbrieven (cease-and-desist) stuurt naar Nitter, lijkt het passend om het essay van TBL te herinneren. Nitter is — was — een eenvoudige frontend voor X die gebruikers in staat stelt tweets te bekijken zonder in te loggen. Zelfs dit kleine gebruik van proxying naar de pagina's is voldoende om juridische dreigementen te ontvangen.

De API van Twitter was in 2007 beroemd om zijn openheid, wat betekende dat duizenden ontwikkelaars gratis clients, tools en analyses konden bouwen. Wat is er gebeurd? Waarom werd dit ingetrokken? Simpel: het netwerk won. De ontwikkelaars waren geen troef meer en de API werd geleidelijk gesloten. Rate limits, prijsstappen, inlogvereisten, gevolgd door technische blokkades van workarounds, en nu brieven van advocaten. Meta volgde een decennium geleden hetzelfde playbook, en het is nu moeilijk te herinneren dat er ooit een Facebook- of Instagram-API was waar het de moeite waard was om op te bouwen.

Dit is waarom Brewster Kahle, oprichter van het Internet Archive, al meer dan tien jaar pleit voor het "openzetten" van het web.

Nitter begon met het gebruik van de API's van X. Toen die gesloten werden, las het openbare webpagina's. En nu er niets meer over is om te sluiten, is de eis dat de broncode wordt verwijderd. Een programma dat openbare berichten weergeeft, wordt behandeld als een omzeilingsinstrument onder computercriminaliteitswetten.

We hebben een probleem met "ommuurde tuinen" (walled gardens). Dit gaat niet veranderen, en de enige optie die we hebben is om opnieuw te beginnen.

Het goede nieuws is dat atproto blijft groeien, ActivityPub veerkrachtig blijft en onze gemeenschap vol gelovigen en bouwers van het open sociale web zit. Aangezien ik aan atproto werk, is dat waar ik het nu over zal hebben.

Interoperabiliteit via SELECT *

Het probleem van de ommuurde tuin is het gevolg van een eenvoudige vraag: hoe voer ik een SELECT * FROM internet uit?

Als je nooit databasecode hebt geschreven: SELECT * FROM users is de manier waarop je een database vraagt om alles wat hij weet over zijn gebruikers. Zodra je dat hebt, kun je het filteren, sorteren en koppelen aan alles wat je verder nog hebt.

Het web werkt historisch gezien niet zo. Het web bestaat uit een paar dozijn bedrijven, die elk een archiefkast beheren met een receptionist voor de deur. Deze receptionist leest je één bestand per keer voor, maar alleen bestanden die je bij naam kunt noemen, zo snel als hij bereid is te lezen, en zolang zijn baas dat toelaat.

Nitter was een lichtgewicht X-reader die prima werkte totdat X de toegang waar het van afhankelijk was, afsloot. Elke API (de "receptionist") is een zakelijke beslissing die nog niet is teruggedraaid.

Maar vergankelijkheid is niet het enige probleem. Zelfs een permanente, gratis API met royale rate limits zou niet genoeg zijn. Applicaties hebben veel betekenisvollere toegang nodig dan API's kunnen bieden.

De beperkingen van API's

  1. Vaste menu's: Je kunt alleen vragen stellen waar iemand al een antwoord op had bedacht. Een API is een vast menu. Het geeft je getPosts(user) en getFollowers(user). Als jouw productidee "berichten van mensen die mijn volgers volgen, gerangschikt op hoe vaak ze geciteerd worden" vereist, is daar geen endpoint voor, en dat zal er ook nooit komen, omdat niemand bij dat bedrijf bouwt voor jouw specifieke product.
  2. Verkeerde vorm: Zelfs de juiste vragen leveren antwoorden in de verkeerde vorm op. Volgers komen per 100. Een account met twee miljoen volgers vereist 20.000 requests. Bij elke redelijke rate limit kost dat uren werk om één vraag over één gebruiker te beantwoorden — waardoor alles wat interactief moet zijn of direct moet aanvoelen, onmogelijk is voordat je begint.
  3. Geen koppelingen tussen "kasten": De interessante vragen zijn bijna altijd cross-service: de berichten van deze persoon tegenover de foto's van die persoon, tegenover de recensies van een derde dienst. Twee receptionisten in twee verschillende gebouwen kunnen niets kruisrefereren, en jij kunt dat ook niet.
  4. Onmogelijkheid van indexering: Je kunt geen data indexeren die je niet bezit. Zoeken, ranking, aanbevelingen, feeds, moderatie-tools — dit alles is gebouwd op indexen over het gehele corpus, ingericht voor de specifieke vragen die jouw product stelt. Je kunt geen index bouwen door een sleutelgat.

Om echt een dienst te bouwen, hebben we de volledige dataset nodig in plaats van een weergave ervan; we hebben het live nodig, waarbij data binnenkomt zodra deze verandert in plaats van via polling; we moeten het kunnen indexeren zoals mijn product dat vereist; we moeten erin kunnen schrijven; en we hebben dat allemaal nodig met een garantie die niet door de kwartaalprioriteiten van één bedrijf kan worden ingetrokken.

Desktop-apps lossen dit op door het bestandssysteem te delen. Internet-apps gebruiken geen bestanden, maar databases. We moeten de database delen.

Als gebruiker wil ik niet vastzitten aan een app, net zoals ik niet vast wil zitten in de kofferbak van een auto. Ik wil een daadwerkelijke vrije markt.

De behoeften voor een open web

  • Persistentie van identiteit: Mijn aanwezigheid en relaties zijn gebouwd rond mijn identiteit. Deze moet langer meegaan dan de app waar ik me bij heb aangemeld.
  • Export van levende (niet dode) data tussen diensten: Het exporteren van archieven van je tweets is nutteloos als oplossing voor accountmigratie, omdat data niet in isolatie bestaat. Als data niet langer "operabel" is — in staat tot verdere bewerkingen door deelnemers in het netwerk — dan is het een statisch archief en nutteloos voor een andere applicatie. Je zou je tweets kunnen uitprinten en ernaar kunnen kijken, denk ik.

Als we willen dat data operabel blijft, zelfs buiten de oorspronkelijke dienst, moeten we de database delen. Dit zijn allemaal problemen die atproto is ontworpen om op te lossen, inclusief open datatoegang, accountmigratie en een live firehose van netwerkactiviteit.

Hoe atproto SELECT * FROM internet mogelijk maakt

Hoe delen we de database? Dat doen we niet. We delen er heel veel van. We creëren een heel netwerk van Personal Data Servers (PDS) waarmee applicaties communiceren.

Hoe gaan we om met apps die complexe SELECT * queries naar onze persoonlijke dataservers sturen? Dat doen we niet. We repliceren de data in logs. Elke applicatie verzamelt kopieën van de data om deze lokaal te kunnen bevragen.

Hoe zorgen we dat apps naar die databases kunnen schrijven? In dit geval doen we dat wel! We laten de apps schrijfacties sturen naar de PDS, die deze op hun beurt weer repliceren naar de andere apps.

Dit laatste is de kern van de intuïtie achter atproto: de write/ingest loop. Bijna elke atproto-app heeft code die er zo uitziet:

// schrijven
pds.putRecord(post)

// absorberen (ingest)
onPut('app.bsky.feed.post', evt => {
  mydb.put('posts', {...})
})

In plaats van te wachten tot de ingest via het netwerk terugkomt, kun je een "short circuit" gebruiken zodat de database van je app sneller kan updaten. De 200 OK van de PDS is de transactionele bevestiging.

Het meer efficiënte patroon ziet er dan zo uit:

// schrijven
pds.putRecord(post)
mydb.put('posts', {...}) // ← optimistisch

// absorberen (ingest)
onPut('app.bsky.feed.post', evt => {
  mydb.put('posts', {...})
})

Werkt het?

Ja. Het netwerk bestaat. Het is live, het is openbaar en je kunt alles nu lezen — vanaf een laptop, zonder toestemming van iemand te vragen. Dit is precies hoe Bluesky, Tangled, Leaflet en een hele reeks anderen nu werken.

Hier zijn enkele statistieken (op het moment van schrijven):

  • 46,1 miljoen accounts op atproto
  • 24,5 miljard records
  • 3,15 miljard daarvan zijn berichten
  • 17,4 miljard daarvan zijn likes
  • 500-1000 schrijfgebeurtenissen per seconde
  • Meer dan 5000 persoonlijke dataservers (PDS)
  • Voldoende apps om een behoorlijke appstore-site van te maken

Het is nog nooit zo makkelijk geweest om toegang te krijgen tot de data met de nieuwe jetstream service:

import { Jetstream, isCreate } from '@bsky/jetstream';
import { app } from '@bsky/sdk/lexicons';

const jetstream = new Jetstream('https://jetstream.us-east.bsky.network');
const collections = [app.bsky.graph.follow, app.bsky.feed.repost, app.bsky.feed.post];

for await (const event of jetstream.live({ collections })) {
  if (isCreate(event, app.bsky.graph.follow)) {
    console.log(`🌱 ${event.did} volgt ${event.commit.record.subject}`);
  } else if (isCreate(event, app.bsky.feed.repost)) {
    console.log(`♻️ ${event.did} repost ${event.commit.record.subject.uri}`);
  } else if (isCreate(event, app.bsky.feed.post) && event.commit.record.reply) {
    console.log(`💭 ${event.did} reageert op ${event.commit.record.reply.parent.uri}`);
  }
}

Stop met het ontvangen van stakingsbrieven. Gebruik in plaats daarvan SELECT * FROM internet.blogposts.

En als je specifiek zoekt naar blogposts op atproto, dan kun je waarschijnlijk het beste standard.site gebruiken.