Getting Started
@skyware/labeler
is a lightweight library for operating a labeler.
Before you start, you will need:
- An existing Bluesky account that will act as the labeler
- A domain
- An SSL certificate for your domain
Dev Note
Before you start, there’s a few things to note. When you emit a label, that label is permanently stored by Bluesky and other AppViews. Deleting your database will not reset that. It will, however, make it very difficult for you to start emitting labels again, as you will be starting again from label 0, which AppViews think they’ve already seen.
It’s recommended that you should use a separate account for development until you know your labeler is working, at which point you can create a new account and delete the database.
You should also only have a single database per labeler account. This means that if you’re switching between development and production with the same account, make sure to copy the database file between machines.
You should not delete your database file unless you will not be using that account as a labeler anymore, in which case you should run npx @skyware/labeler clear
afterwards to return it to a regular account.
Setup
If you already have a labeler set up, via Ozone or otherwise, you can skip this step.
To initialize the Bluesky account as a labeler, run the following command:
npx @skyware/labeler setup
The command will walk you through logging in, then ask for the URL where your labeler server will be located. This must be an HTTPS URL. A confirmation code will then be sent to your email. Once you’ve entered the code, a signing key will be generated if you did not provide one. Be sure to save this key, as you will need it to operate the labeler.
Once complete, you’ll have the option to begin defining labels that your labeler will apply. You can also do this later by running npx @skyware/labeler label add
. For each label, you must define the following information:
? Identifier (non-user-facing, must be unique, 100 characters max): » A unique identifier for the label, used internally.
? Name (user-facing, 64 characters max): » The name that users will see for the label.
? Description (user-facing): » A description of the label.
? Does the user need to have adult content enabled to configure this label? » (y/N)
? Label severity:
Informational - Displays an information symbol next to the label
Alert - Displays a warning symbol next to the label
None - The label will not be displayed on content
? Should this label hide content?
Content - All content, text or media, will be hidden if labeled
Media - Only media will be hidden if labeled
None - The label will not hide content
? What should the default setting be for a new subscriber?
Ignore - The label will not be displayed by default
Warn - The label will be displayed on content, but will not hide it from feeds
Hide - The label will be displayed on content, and labeled content will be hidden from feeds
Running the Labeler
import { class LabelerServer
LabelerServer } from "@skyware/labeler";
const const server: LabelerServer
server = new new LabelerServer(options: LabelerOptions): LabelerServer
Create a labeler server.LabelerServer({
LabelerOptions.did: string
The DID of the labeler account.did: var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The process.env
property returns an object containing the user environment.
See `environ(7)`.
An example of this object looks like:
{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker
threads.
In other words, the following example would not work:
$ node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
import { env } from 'process';
env.foo = 'bar';
console.log(env.foo);
Assigning a property on process.env
will implicitly convert the value
to a string. **This behavior is deprecated.** Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from 'process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'
Use delete
to delete a property from process.env
.
import { env } from 'process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from 'process';
env.TEST = 1;
console.log(env.test);
// => 1
Unless explicitly specified when creating a Worker
instance,
each Worker
thread has its own copy of process.env
, based on its
parent thread’s process.env
, or whatever was specified as the env
option
to the Worker
constructor. Changes to process.env
will not be visible
across Worker
threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons.env.string
LABELER_DID,
LabelerOptions.signingKey: string
The private signing key used for the labeler.
If you don't have a key, generate and set one using
{@link
plcSetupLabeler
}
.signingKey: var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The process.env
property returns an object containing the user environment.
See `environ(7)`.
An example of this object looks like:
{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker
threads.
In other words, the following example would not work:
$ node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
import { env } from 'process';
env.foo = 'bar';
console.log(env.foo);
Assigning a property on process.env
will implicitly convert the value
to a string. **This behavior is deprecated.** Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from 'process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'
Use delete
to delete a property from process.env
.
import { env } from 'process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from 'process';
env.TEST = 1;
console.log(env.test);
// => 1
Unless explicitly specified when creating a Worker
instance,
each Worker
thread has its own copy of process.env
, based on its
parent thread’s process.env
, or whatever was specified as the env
option
to the Worker
constructor. Changes to process.env
will not be visible
across Worker
threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons.env.string
SIGNING_KEY
});
const server: LabelerServer
server.LabelerServer.start(port: number, callback: (error: Error | null, address: string) => void): void (+1 overload)
Start the server.start(14831, (error: Error | null
error) => {
if (error: Error | null
error) {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A Console
class with methods such as console.log()
, console.error()
andconsole.warn()
that can be used to write to any Node.js stream.
* A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without callingrequire('console')
.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.error(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stderr
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to `printf(3)` (the arguments are all passed to util.format()
).
const code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr
If formatting elements (e.g. %d
) are not found in the first string then util.inspect()
is called on each argument and the resulting string
values are concatenated. See util.format()
for more information.error("Failed to start server:", error: Error
error);
} else {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
* A Console
class with methods such as console.log()
, console.error()
andconsole.warn()
that can be used to write to any Node.js stream.
* A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without callingrequire('console')
.
_**Warning**_: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to `printf(3)` (the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.log("Labeler server running on port 14831");
}
});
The LabelerServer
class takes an object argument with the following properties:
did
: The DID of the labeler account.signingKey
: The signing key used to sign labels. This should be the same key you received when setting up the labeler, if you used the setup CLI command.auth
(optional): A function that takes a DID and returns a boolean indicating whether the DID is authorized to emit labels. If not provided, only the labeler account can emit labels.dbPath
(optional): The path to the database file. Defaults to./labels.db
.dbUrl
(optional): The URL of the database. Use this instead ofdbPath
if you want to use a remote libSQL database.dbToken
(optional): The authentication token for the database. Required if a remotedbUrl
is provided.
After initializing the server, call the start
method with the port number to listen on and a callback that will be called when the server starts. If an error occurs, the callback will be called with the error.
Once your labeler is running, you will need to point your labeler’s domain to the server and set up SSL. You can use a tool like Caddy to do this.
If all has gone well, you’re ready to start labeling content!
await const server: LabelerServer
server.LabelerServer.createLabel(label: CreateLabelData): Promise<SavedLabel>
Create and insert a label into the database, emitting it to subscribers.createLabel({
// If you're labeling a record, this should be an at:// URI pointing to the record.
// If you're labeling an account, this should be the account's DID.
CreateLabelData.uri: string
The subject of the label. If labeling an account, this should be a string beginning with did:
.uri: "did:plc:427uaandx74txvzakxthrjwu",
// Assuming you've previously defined a label with the identifier "cool-cat".
CreateLabelData.val: string
The label value.val: "cool-cat",
});
To undo a label, emit the same label with the neg
property set to true
to neg
ate the label.
await const server: LabelerServer
server.LabelerServer.createLabel(label: CreateLabelData): Promise<SavedLabel>
Create and insert a label into the database, emitting it to subscribers.createLabel({
CreateLabelData.uri: string
The subject of the label. If labeling an account, this should be a string beginning with did:
.uri: "did:plc:427uaandx74txvzakxthrjwu",
CreateLabelData.val: string
The label value.val: "cool-cat",
CreateLabelData.neg?: boolean | undefined
Whether this label is negating a previous instance of this label applied to the same subject.neg: true
});
Next, read on to learn about Automated Labeling.