Můj první Bluesky labeler

Co jsou to labelery na sociální síti Bluesky? A jak vytvořit ten svůj? V článku popíšu, jak jsem zprovoznil labeler, kde se každý může označit podle kraje, ve kterém žije.

Labelery na Bluesky

Na sociální sítí Bluesky s moderací obsahu pomáhají labely, tedy značky, které je možné připnout k nějakému uživateli nebo k příspěvku. Tradičním příkladem labelu, který známe i z jiných sítí, je označení nahoty nebo násilného obsahu. Někteří uživatelé Bluesky si mohou vybrat, že se jim takový obsah nebude vůbec zobrazovat. JIní mohou mít nastavené rozmazání takových příspěvků, dokud si je nerozkliknou.

Podobným způsobem jde na Bluesky označit celý profil. To umožňuje vznik labelerů jako je NAFO Forum Moderation Service, jehož autoři označí účty sdílející dezinformace, a vy si můžete vybrat, zda příspěvky od těchto účtů chcete mít označené, nebo zda je chcete úplně skrýt.

Je důležité zdůraznit, že takový label se nezobrazí plošně všem uživatelům, pouze těm, kteří daný labeler sledují. Každý uživatel si tak může vybrat jiný labeler jakožto moderační autoritu, které bude důvěřovat s označováním nebo skrýváním obsahu.

Síť nakloněná k tomu, aby lidé takové labelery vytvářeli, a uživatelé rychle přišli na to, že se tato služba dá využít nejenom k označování škodlivého obsahu. Začaly vznikat labelery, které uživatelé dobrovolně využívají k tomu, aby svůj profil (a každý svůj příspěvek) dobrovolně označili podle předvolených možností.

Náhled příspěvku od Matěj Voslař. Pod jménem jsou zobrazeny labely: Liberecký kraj, he/him, Green, Speak Now

Můj profil je mimo jiné přihlášený k Pronouns Labeling System, a My Favorite Color, které na mém profilu zobrazují má zájmena nebo oblíbenou barvu. Jak už jsem ale zmínil výše, žádný z těchto labelů na mém profilu neuvidíte, pokud tyto labelery nesledujete.

Vytvoření vlastního labeleru

Nějakou dobu jsem přemýšlel, že bych také zprovoznil vlastní labeler. Takový, kde si čeští uživatelé mohou vybrat vlastní kraj, a lehce tak poznat, s kterými uživateli mají geograficky blízko. Takový nástroj může sloužit ke sblížení některých uživatelů, ale pokud má někdo starost o svoje soukromí, nebo ho poloha ostatních uživatelů nezajímá, stačí jednoduše, aby labeler nepoužíval.

Naštěstí lidé, kteří vytváří nástroje pro Bluesky tak často dělají veřejně a jejich kód je volně k nahlédnutí i k využití. K zprovoznění mého labeleru Kraje v Česku jsem okopíroval kód, který Alice, prominentní tvůrkyně doplňků k Bluesky, dala volně k použití jako Bluesky Labeler Start Kit. Její kód má dvě hlavní části: @skyware/labeler, javascriptovou knihovnu/nástroj pro zveřejňování labelů směrem k síti Bluesky. a pak dále @skyware/jetstream, který naslouchá eventům na síti, jako například když vám někdo lajkne příspěvek. Tak jsou totiž labelery, jimiž uživatelé sami sebe dobrovolně označují, obvykle ovládány. Účet labeleru zveřejní pro každý label jeden příspěvek, a jeho lajknutím si uživatel může nechat label přidat na svůj profil.

K založení labeleru stačí mít druhý účet na Bluesky, který člověk promění v labeler pomocí nástroje @skyware/labeler, konkrétně příkazem jako npx @skyware/labeler setup . Návodný interface provede proměnu daného účtu v labeler a také umožní navolit si jednotlivé labely, které budou k dispozici. Pro labely, ke kterým se uživatelé hlásí dobrovolně, je potřeba zvolit možnosti: Informational - None - Warn, v jiném případě by mohl vzniknout label určený ke skrývání obsahu.

Konzolové rozhraní pro přidání nových labelů v knihovně/nástroji @skyware/labeler

Nastavení labelů v příkazové řádce pomocí nástrojů @skyware/labeler setup nebo @skyware/labeler label add.

Dále je také potřeba vytvořit příspěvky, lajkováním kterých budou uživatelé ovládat labeler. To je možné udělat ručně, případně repozitář od Alice nabízí nástroj set-posts, který příspěvky vytvoří za vás podle obsahu souboru src/constants.ts a následně zobrazí unikátní identifikátor každého příspěvku rkey. Poté je třeba vrátit se do souboru s konstantami a rkey ke každému labelu doplnit. Pokud jste příspěvky vytvořili ručně, budete muset každý rozkliknout a zkopírovat poslední část url adresy, například 3lc4jymjjlq23 v adrese https://bsky.app/profile/czkraje.bsky.social/post/3lc4jymjjlq23.

Také pole identifier se musí shodovat s hodnotou, kterou uživatel zadal při setupování labeleru jako Identifier (non-user-facing... Tato nastavení zaručí, že aplikace pozná správný příspěvek podle rkey, a následně přidá uživateli správný label.

Alicin kód obsahuje také nástroj set-labels , který by měl ušetřit čas zadáváním labelů v setup fázi, a místo toho automaticky přidat labely podle souboru konstant. Z nějakého důvodu se ale labely přidané tímto nástrojem nezobrazovaly u příspěvků, proto jsem všechny labely zadával ručně.

Screenshot profilu czkraje.bsky.social, poté co byl proměněn na labeler účet.

Takto by měl labeler vypadat pro ostatní uživatele po dokončení setupu.

Všechny tyto kroky je možné dokončit z vlastního počítače, aplikace labeleru ale bude muset konstatně běžet na serveru, aby mohla naslouchat eventům a zveřejňovat informace o přidání labelů, které bude naopak konzumovat Bluesky. Podmínkou také je, že aplikace bude běžet na HTTPS adrese, kterou jste zadali při setup fázi.

Pokud se vám povedlo server zprovoznit (a níže popíšu, jak jsem toho dosáhl já), měli byste na endpointu /xrpc/com.atproto.label.queryLabels vidět output. V případě úplně nového serveru, kde se ještě nikdo nepřihlásil uvidíte něco jako: {"cursor":"0","labels":[]}.

Pokud máte vlastní server, nebo například instanci na které vám běží docker s nginx, můžete nastavit reverse proxy pro 127.0.0.1:4002 a dovolit aplikaci komunikovat s vnějškem pomocí HTTPS. Na spravovaném serveru ale může být obtížnější, případně nemožné, dosáhnout stejného výsledku. Abych mohl použít existující Alicin kód s co nejmenším množstvím změn, a bez nutnosti vlastnit server, vyzkoušel jsem dvě služby, které nabízejí Bun runtime (níže popíšu, proč toto byla chyba, a měl jsem raději zvolit službu s Node.js runtime).

Neúspěch s Render.com

Nejprve jsem vyzkoušel Render.com, která nabízí pro webové aplikace i free tier, a tak jsem doufal, že mi pomůže naplnit můj cíl aniž by mě to cokoliv stálo. V základu také nabídne subdoménu s aktivovaným HTTPS, což byla nutná podmínka.

Kamenem úrazu se ale staly omezené možnosti nastavení. Render vyžaduje, aby vaše aplikace běžela na 0.0.0.0:8080. Port, na kterém bude @skyware/labeler server běžet, se dá změnit třeba nastavením proměnných prostředí. Host je ale vždy 127.0.0.1 a pro jeho změnu bych musel změnit samotný kód knihovny.

Hledal jsem tedy dále.

Rosti.cz

Další služba, která mi byla vyhledávačem doporučena pro schopnost hostovat aplikace s Bun runtimem, bylo rosti.cz. Pokud se obejdu s nejmenším množstvím zdrojů (0.25 CPU, 512B RAM), celá sranda by mě měla vyjít na zhruba 80 CZK měsíčně.

Je nutné se seznámit s celým ekosystémem, a na základě dokumentace zkontrolovat veškerá nastavení, nad kterými Rosti dává celkem velkou kontrolu, včetně možnosti přistupovat do serveru pomocí SSH. Můžeme tak například použít konzoli k nainstalování potřebných Node balíčků.

Nastavení portu

Aplikace bude defaultně poslouchat na 127.0.0.1:8080 a o její spojení s vnějším světem se postará nginx. Stačí změnit nastavení portu, případně aplikaci říct, aby použila port nastavený v nginx.

Nastavení supervisoru

Správa procesů probíhá pomocí nastroje supervisorctl a je tedy nutné nastavit jej tak, aby spouštěl správný proces. V mém případě mě zajímalo pouze spuštění programu src/main.ts a tak jsem v nastavení supervisoru upravil hodnotu command, aby přímo spustila daný soubor. Poté je také nutné nastavení supervisoru znovu načíst.

command=/srv/bin/primary_tech/bun run /srv/app/src/main.ts

Přístrojová deska ukazuje OK stav DNS, LB i DB, ale v případě App hlásí, že nebyl vrácen 2xx status kód.

Přístrojová deska na rosti.cz bude hlásit chybu, že App na / nevrací kladnou HTTP odpověď. To je v pořádku, nás zajímá hlavně to, jestli aplikace odpovídá na /xrpc/com.atproto.label.queryLabels.

Potíže s WebSockets

Ověřil jsem si na /xrpc/com.atproto.label.queryLabels, že server labeleru běží. Po lajknutí příspěvku s libovolnám krajem se ale obsah stránky neupdatoval, a můj profil nebyl nijak označen. Náhled do logů (soubor srv/log/bun.log) prozradil, že selhává JetStream, který by měl pomocí WebSockets získávat oznámení o lajcích z Bluesky.

Podle dokumentace jsem upravil nastavení nginx, aby podporovalo websockets, stále však přetrvávala chyba Jetstream error: Invalid binaryType: blob. Netuše jak dál jsem se poradil s ChatGPT. Ten mi poradil správně, u instance WebSocket je potřeba nastavit vlastnost binaryType na hodnotu "arraybuffer".

Bohužel v tomto případě je instance WebSocket vytvořena přímo v knihovně JetStream. Ačkoliv je podle všeho možné specifikovat konkrétní implementaci typu WebSocket, kterou JetStream použije, nastavení binaryType budou ignorována i pokud je náš typ definuje. Kód níže vede ke stejné chybě:

import WebSocket from 'ws';

class MyWebSocket extends WebSocket {
  constructor(address: string, protocols?: string | string[] | undefined, options?: any | undefined) {
    super(address, protocols, options);
    this.binaryType = 'arraybuffer';
  }
}

const jetstream = new Jetstream({
  ws: MyWebSocket, // ani při specifikaci vlastního typu NEFUNGUJE
  wantedCollections: [WANTED_COLLECTION],
  endpoint: FIREHOSE_URL,
  cursor: cursor,
});

TypeScript není moje silná stránka, tak je možné, že jsem někde udělal chybu. Ale bez dalšího východiska jsem se rozhodl na prasáka přepsat modul @skyware/jetstream/dist/index.js. Pod řádkem 49, kde dochází k vytvoření instance WebSocket, jsem přidal následující řádek, což vyřešilo všechny mé potíže.

this.ws.binaryType = "arraybuffer";

Samozřejmě, že toto není dlouhodobě udržitelné řešení. Uvažuji nad tím, že bych přispěl ke kódu knihovny JetStream možností definovat binaryType už v konstruktoru.

Update ke zmíněnému problému s WebSockets

Po nějaké době strávené zápasením s různými způsoby jak explicitně nastavit binaryType pro WebSockets, když už jsem měl rozepsaný pull request vůči knihovně JetStream, uvědomil jsem si omyl, kterého jsem se při napodobování Labeler Starter Kitu dopustil. Alice sice používá Bun pro správu balíčků, a volá s ním nástroje jako lint. Pro spuštění skriptu ale používá příkaz npx tsx src/main.ts.

Pokud spustím skript také pomocí npx tsx případně pomocí bun tsx, zmíněná chyba s binaryType se neobjeví. Druhý z příkazů ale nelze na Rosti.cz použít ke spuštění aplikace, a tak bych musel nejprve změnit runtime na Node.js. V Bun prostředí mi nezbývá než dále fungovat s patchnutou knihovnou, nebo přecijenom zkusit přemluvit autora knihovny, že by měl dovolit explicitně nastavit binaryType z kódu který knihovnu volá, aby byly uspokojeny potřeby prostředí Bunu.

Závěr

Pokud se chystáte vytvořit vlastní labeler, snad vám moje poznámky byly nápomocné. Rád bych viděl co nejvíce lidí zapojit se do tvorby nástrojů pro Bluesky.

Napsat komentář