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.
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.