Vai al contenuto principale
Labeler è una funzionalità per utenti avanzati. Poiché deve essere eseguito come server continuativamente, è pensato per chi usa Laravel Forge o è in grado di configurare un server autonomamente. Non è consigliato ai principianti di Laravel. Non c’è supporto.Laravel Cloud non è supportato. Labeler richiede di avviare un server WebSocket sempre attivo e di modificare la configurazione di nginx; Laravel Cloud non permette di configurare nginx, quindi non è possibile eseguire Labeler.

Cos’è Labeler

Labeler è un servizio che applica etichette (label) ai contenuti su AT Protocol (Bluesky). Si può usare per moderazione, classificazione dei contenuti, filtri personalizzati e altro. È necessario comprendere in anticipo il concetto di Labeler. Anche gli starter kit per altri linguaggi sono un utile riferimento. Implementazione di esempio:

Preparazione

Per far funzionare Labeler ti serve quanto segue.
  • Un nuovo account Bluesky dedicato al Labeler (non usare l’account personale che usi abitualmente)
  • Un nuovo progetto Laravel dedicato al Labeler (è consigliato tenere i progetti separati)
  • Un (sotto)dominio
  • Un server Linux di produzione, come VPS o AWS EC2 (Laravel Cloud, Laravel Vapor e Vercel non sono utilizzabili)
Se puoi usare solo hosting condiviso, gestire un Labeler è difficile.

Installazione di pacchetti aggiuntivi

composer require workerman/workerman revolt/event-loop

Configurazione

Per prima cosa genera la chiave privata.
php artisan bluesky:labeler:new-private-key
Aggiungi la chiave privata generata e i valori di configurazione correlati a .env.
BLUESKY_LABELER_DID=did:plc:***
BLUESKY_LABELER_IDENTIFIER=***.bsky.social
BLUESKY_LABELER_APP_PASSWORD=

BLUESKY_LABELER_PRIVATE_KEY=""

Creazione della classe Labeler

Crea una classe Labeler personalizzata che estende AbstractLabeler. Il file puoi collocarlo dove preferisci.
namespace App\Labeler;

use Revolution\Bluesky\Labeler\AbstractLabeler;

readonly class ArtisanLabeler extends AbstractLabeler
{
    // Implementa i vari metodi
}
Il pacchetto si occupa della maggior parte del lavoro del Labeler, quindi implementi soltanto le parti che vuoi personalizzare.
Implementazione di esempio: ArtisanLabeler.php

labels()

Restituisce le definizioni delle label. Le costanti definite nello starter kit skyware sono un buon riferimento.
use Revolution\Bluesky\Labeler\LabelDefinition;
use Revolution\Bluesky\Labeler\LabelLocale;

public function labels(): array
{
    return [
        new LabelDefinition(
            identifier: 'artisan',
            locales: [
                new LabelLocale(
                    lang: 'en',
                    name: 'artisan',
                    description: 'Web artisan',
                ),
            ],
            severity: 'inform',
            blurs: 'none',
            defaultSetting: 'warn',
            adultOnly: false,
        ),
    ];
}

subscribeLabels()

Viene chiamato subito dopo la connessione WebSocket. Restituisci SubscribeLabelResponse come iterator.
use Revolution\Bluesky\Labeler\Labeler;
use Revolution\Bluesky\Labeler\LabelerException;
use Revolution\Bluesky\Labeler\Response\SubscribeLabelResponse;

/**
 * @return iterable<SubscribeLabelResponse>
 */
public function subscribeLabels(?int $cursor): iterable
{
    if (is_null($cursor)) {
        return null;
    }

    // Se devi restituire una risposta di errore lancia sempre LabelerException
    if ($cursor > Label::max('id')) {
        throw new LabelerException('FutureCursor', 'Cursor is in the future');
    }

    foreach (Label::oldest()->where('id', '>', $cursor)->lazy() as $label) {
        $arr = $label->toArray();
        $arr = Labeler::formatLabel($arr);

        yield new SubscribeLabelResponse(
            seq: $label->id,
            labels: [$arr],
        );
    }
}

emitEvent()

Viene chiamato quando arriva una richiesta di aggiunta o rimozione di una label. Restituisci UnsignedLabel come iterator.
use Illuminate\Http\Request;
use Revolution\Bluesky\Labeler\LabelerException;
use Revolution\Bluesky\Labeler\UnsignedLabel;

/**
 * @return iterable<UnsignedLabel>
 *
 * @link https://docs.bsky.app/docs/api/tools-ozone-moderation-emit-event
 */
public function emitEvent(Request $request, ?string $did, ?string $token): iterable
{
    $type = data_get($request->input('event'), '$type');
    if ($type !== 'tools.ozone.moderation.defs#modEventLabel') {
        throw new LabelerException('InvalidRequest', 'Unsupported event type');
    }

    $subject = $request->input('subject');
    $uri = data_get($subject, 'uri', data_get($subject, 'did'));
    $cid = data_get($subject, 'cid');

    $createLabelVals = (array) data_get($request->input('event'), 'createLabelVals');
    $negateLabelVals = (array) data_get($request->input('event'), 'negateLabelVals');

    foreach ($createLabelVals as $val) {
        yield new UnsignedLabel(
            uri: $uri,
            cid: $cid,
            val: $val,
            src: config('bluesky.labeler.did'),
            cts: now()->micro(0)->toISOString(),
        );
    }

    foreach ($negateLabelVals as $val) {
        yield new UnsignedLabel(
            uri: $uri,
            cid: $cid,
            val: $val,
            src: config('bluesky.labeler.did'),
            cts: now()->micro(0)->toISOString(),
            neg: true,
        );
    }
}

saveLabel()

Salva la label firmata nel database. Restituisce un SavedLabel.
use Revolution\Bluesky\Labeler\SavedLabel;
use Revolution\Bluesky\Labeler\SignedLabel;

public function saveLabel(SignedLabel $signed, string $sign): ?SavedLabel
{
    // App\Models\Label lo devi creare tu
    $saved = Label::create($signed->toArray());

    return new SavedLabel(
        $saved->id,
        $signed,
    );
}
Per migrazione e modello Eloquent, consulta gli esempi nel pacchetto.

createReport()

Viene chiamato quando arriva un ricorso o simile da un utente.
use Illuminate\Http\Request;

/**
 * @link https://docs.bsky.app/docs/api/com-atproto-moderation-create-report
 */
public function createReport(Request $request): array
{
    // Elabora la segnalazione e restituisci un array
    // Campi obbligatori: id, reasonType, reason, subject, reportedBy, createdAt

    return [
        'id' => 1,
        'reasonType' => $request->input('reasonType'),
        'reason' => $request->input('reason', ''),
        'subject' => $request->input('subject'),
        'reportedBy' => '',
        'createdAt' => now()->toISOString(),
    ];
}

queryLabels()

Query delle label via API HTTP al posto del WebSocket. Non è usato da Bluesky ufficiale ma da terze parti. Se non ti serve, restituisci un array vuoto.
use Illuminate\Http\Request;

/**
 * @link https://docs.bsky.app/docs/api/com-atproto-label-query-labels
 */
public function queryLabels(Request $request): array
{
    return [];
}

Registrazione in AppServiceProvider

Registra la classe Labeler creata in AppServiceProvider::boot().
use Revolution\Bluesky\Labeler\Labeler;
use App\Labeler\ArtisanLabeler;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Labeler::register(ArtisanLabeler::class);
    }
}

Setup dell’account

Inizializza l’account come Labeler.
In questo comando inserisci l’effettiva password dell’account e non l’app password. Durante l’operazione ti arriverà un’email di conferma “PLC Update Operation Requested”: segui le istruzioni del comando per inserirla.
php artisan bluesky:labeler:setup
Se l’URL dell’endpoint è configurato correttamente puoi eseguirlo anche dall’ambiente locale.

Dichiarazione delle definizioni di label

Registra le definizioni di label sull’account Labeler.
php artisan bluesky:labeler:declare-labels
Anche questo comando può essere eseguito dall’ambiente locale.

Altri comandi

Eliminazione delle definizioni di label:
php artisan bluesky:labeler:delete-labels
Riportare l’account Labeler ad account normale:
php artisan bluesky:labeler:restore

Esecuzione su Laravel Forge

Dopo aver abilitato SSL, avvia il server Labeler con la seguente configurazione.

Configurazione nginx

Aggiungi 3 blocchi location alla configurazione nginx di Forge.
# WebSocket: sottoscrizione alle label
location /xrpc/com.atproto.label.subscribeLabels
{
    proxy_pass http://127.0.0.1:7000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header X-Real-IP $remote_addr;
}

# HTTP: invio di eventi
location /xrpc/tools.ozone.moderation.emitEvent
{
    proxy_pass http://127.0.0.1:7001;
    proxy_http_version 1.1;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Connection "";
}

# Health check
location /xrpc/_health
{
    proxy_pass http://127.0.0.1:7001;
    proxy_http_version 1.1;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Connection "";
}

Script di deploy

Al deploy ferma il server Labeler. Dopo lo stop, Supervisor lo riavvierà automaticamente.
# Procedura di deploy standard
$FORGE_PHP artisan bluesky:labeler:server stop

# Se il comando sopra non funziona, riavvia direttamente il daemon
sudo -S supervisorctl restart daemon-{id}:*

Configurazione del processo in background (daemon)

Nella schermata di configurazione dei background process di Forge, seleziona la scheda Custom invece di Queue Worker. Comando:
php artisan bluesky:labeler:server start
Se lo avvii insieme a Jetstream o Firehose, specifica le opzioni. Non puoi eseguirlo contemporaneamente ai comandi bluesky:ws o bluesky:firehose.
# Avvio insieme a Jetstream
php artisan bluesky:labeler:server start --jetstream

# Filtrando collezioni specifiche
php artisan bluesky:labeler:server start --jetstream -C app.bsky.graph.follow -C app.bsky.feed.like

# Avvio insieme a Firehose
php artisan bluesky:labeler:server start --firehose

Aggiunta di label

Come applichi le label dipende dall’implementazione della tua applicazione. Nell’esempio, usando il sistema di eventi di Laravel, viene applicata una label “quando ricevi un follow”.
// Idea per app/Listeners/FollowListener.php

use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Events\Jetstream\JetstreamCommitMessage;
use Revolution\Bluesky\Types\RepoRef;

public function handle(JetstreamCommitMessage $event): void
{
    $message = $event->message;
    $followerDid = data_get($message, 'did');

    // Applica la label tramite l'API Ozone di Bluesky
    Bluesky::login(
        identifier: config('bluesky.labeler.identifier'),
        password: config('bluesky.labeler.password'),
    )->createLabels(
        subject: RepoRef::to($followerDid),
        labels: ['artisan'],
    );
}
Per gestire eventuali eventi persi, è consigliato applicare label anche tramite task scheduler.
Ultima modifica il 13 luglio 2026