Automated Labeling

This guide assumes you’ve followed the steps in Getting Started and have a labeler set up. If you haven’t, do that now!

Now that you’re running a labeler server, you might want to start labeling content based on user interaction; for instance, liking a post or sending a DM. This guide will walk you through building a bot that will allow users to select labels by liking posts.

For this guide, we’ve set up an example labeler with four labels for a user to choose from: fire, water, air, and earth. These labels have been set to “inform” because they’re intended to be used for informational purposes, “warn” severity so that they appear on user profiles and posts, and no blur because these are not moderation labels.

Here’s what the profile we’ll be working with looks like, all set up, though it’s not ready to start applying labels quite yet.

A labeler profile menu. There are four labels to choose from: Fire, Water, Air, and Earth. Each label is set to "Show badge"

Setting up

Start by installing @skyware/labeler and @skyware/bot.

npm install @skyware/labeler @skyware/bot

You’ll also need to create a .env file in the root of your project. You can use the dotenv package to load these credentials into your project.

LABELER_DID=did:...
LABELER_PASSWORD=...
SIGNING_KEY=...

LABELER_DID is the DID of the labeler account you created in the previous guide. You can look this up at internect.info.

LABELER_PASSWORD is the password you set when you created the labeler account.

SIGNING_KEY is the signing key you received when setting up the labeler, if you used the CLI for setup.

Creating Posts

The first step is to create posts that users can like to receive labels. Using @skyware/bot, you can create posts with the Bot#post method. Create a file called post.ts with the following code:

import { class Bot
A bot that can interact with the Bluesky API.
Bot
} from "@skyware/bot";
const const bot: Botbot = new new Bot({ service, langs, emitEvents, emitChatEvents, rateLimitOptions, cacheOptions, eventEmitterOptions, }?: BotOptions): Bot
Create a new bot.
@paramoptions Configuration options.
Bot
();
await const bot: Botbot.Bot.login({ identifier, password }: BotLoginOptions): Promise<OutputSchema>
Log in with an identifier and password.
@paramoptions The bot account's identifier and password.@returnsSession data.
login
({
BotLoginOptions.identifier: string
The bot account's email, handle, or DID.
identifier
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_DID,
BotLoginOptions.password: string
The bot account's password.
password
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_PASSWORD,
}); const const post: PostReferencepost = await const bot: Botbot.Bot.post(payload: PostPayload, options?: BotPostOptions): Promise<PostReference>
Create a post.
@parampayload The post payload.@paramoptions Optional configuration.@returnsA reference to the created post.
post
({ PostPayload.text: string | RichText
The post text. Can be a string or a RichText instance containing facets.
text
: "Like the replies to this post to receive labels.", PostPayload.threadgate?: { allowMentioned?: boolean; allowFollowing?: boolean; allowLists?: Array<string> | Array<List>; } | undefined
An optional threadgate to be applied to the post.
threadgate
: { allowLists?: string[] | List[] | undefined
Lists or AT URIs pointing to lists whose members are allowed to reply.
allowLists
: [] } });
const const firePost: PostReferencefirePost = await const post: PostReferencepost.PostReference.reply(payload: PostPayload, options?: BotPostOptions): Promise<PostReference>
Reply to the post.
@parampayload The post payload.@paramoptions Optional configuration.@returnsA reference to the created post.
reply
({ PostPayload.text: string | RichText
The post text. Can be a string or a RichText instance containing facets.
text
: "Fire!" });
const const waterPost: PostReferencewaterPost = await const post: PostReferencepost.PostReference.reply(payload: PostPayload, options?: BotPostOptions): Promise<PostReference>
Reply to the post.
@parampayload The post payload.@paramoptions Optional configuration.@returnsA reference to the created post.
reply
({ PostPayload.text: string | RichText
The post text. Can be a string or a RichText instance containing facets.
text
: "Water!"});
const const airPost: PostReferenceairPost = await const post: PostReferencepost.PostReference.reply(payload: PostPayload, options?: BotPostOptions): Promise<PostReference>
Reply to the post.
@parampayload The post payload.@paramoptions Optional configuration.@returnsA reference to the created post.
reply
({ PostPayload.text: string | RichText
The post text. Can be a string or a RichText instance containing facets.
text
: "Air!" });
const const earthPost: PostReferenceearthPost = await const post: PostReferencepost.PostReference.reply(payload: PostPayload, options?: BotPostOptions): Promise<PostReference>
Reply to the post.
@parampayload The post payload.@paramoptions Optional configuration.@returnsA reference to the created post.
reply
({ PostPayload.text: string | RichText
The post text. Can be a string or a RichText instance containing facets.
text
: "Earth!" });
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
@seesource
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.
@sincev0.1.100
log
(
`Fire: ${const firePost: PostReferencefirePost.PostReference.uri: string
The post's AT URI.
uri
}\n`,
`Water: ${const waterPost: PostReferencewaterPost.PostReference.uri: string
The post's AT URI.
uri
}\n`,
`Air: ${const airPost: PostReferenceairPost.PostReference.uri: string
The post's AT URI.
uri
}\n`,
`Earth: ${const earthPost: PostReferenceearthPost.PostReference.uri: string
The post's AT URI.
uri
}\n`,
); // Fire: at://did:.../app.bsky.feed.post/... // Water: at://did:.../app.bsky.feed.post/... // Air: at://did:.../app.bsky.feed.post/... // Earth: at://did:.../app.bsky.feed.post/...

The post method returns a PostReference object, which you can use to reply to the post. The PostReference#reply method will return a reference to the reply. We’re using the threadgate property to prevent anyone else from replying to the post, by setting the allowed lists of users to an empty array.

Transpile this code if necessary and run it. You should see the AT URI of each post printed to the console. Take note of these, as you’ll need them later to process likes.

A screenshot of a post with the text "Like the replies to this post to receive labels." There are four replies with the text "Fire!", "Water!", "Air!", and "Earth!".

Running the Labeler

Now that you’ve got the posts you need, create a new file called labeler.ts. This is where you’ll run your labeler server and listen for likes.

import { class LabelerServerLabelerServer } from "@skyware/labeler";
import { class Bot
A bot that can interact with the Bluesky API.
Bot
} from "@skyware/bot";
const const server: LabelerServerserver = new new LabelerServer(options: LabelerOptions): LabelerServer
Create a labeler server.
@paramoptions Configuration options.
LabelerServer
({
LabelerOptions.did: string
The DID of the labeler account.
did
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_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.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringSIGNING_KEY,
}); const server: LabelerServerserver.LabelerServer.start(port: number, callback?: (error: Error | null, address: string) => void): void
Start the server.
@paramport The port to listen on.@paramcallback A callback to run when the server is started.
start
(14831, (error: Error | nullerror) => {
if (error: Error | nullerror) { 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
@seesource
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.
@sincev0.1.100
error
("Failed to start: ", error: Errorerror);
} 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
@seesource
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.
@sincev0.1.100
log
("Listening on port 14831");
} }); const const bot: Botbot = new new Bot({ service, langs, emitEvents, emitChatEvents, rateLimitOptions, cacheOptions, eventEmitterOptions, }?: BotOptions): Bot
Create a new bot.
@paramoptions Configuration options.
Bot
();
await const bot: Botbot.Bot.login({ identifier, password }: BotLoginOptions): Promise<OutputSchema>
Log in with an identifier and password.
@paramoptions The bot account's identifier and password.@returnsSession data.
login
({
BotLoginOptions.identifier: string
The bot account's email, handle, or DID.
identifier
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_DID,
BotLoginOptions.password: string
The bot account's password.
password
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_PASSWORD,
});

A reminder that you’ll need to set up reverse proxying so that the domain you’re using for the labeler points to the server running this code, on the port you choose for server.start. You can use a tool like Caddy to do this.

If no errors are printed to the console, you’re ready to start labeling!

Labeling

Next, we’ll build on this to listen for likes and apply labels to users. To start, we can use the Bot#on method to listen for likes. This method will be called whenever a user likes a post, feed generator, or the labeler profile. Add the following code to the end of labeler.ts:

// Modify your import to include the Post class
import { class Bot
A bot that can interact with the Bluesky API.
Bot
, class Post
Represents a post on Bluesky.
Post
} from "@skyware/bot";
// ... const bot: Botbot.Bot.on(event: "like", listener: (event: { subject: Post | FeedGenerator | Labeler; user: Profile; uri: string; }) => void): Bot (+9 overloads)
Emitted when one of the bot's posts is liked.
@paramlistener A callback function that receives the subject that was liked, the user who liked it, and the like's AT URI.
on
("like", async ({ subject: Post | FeedGenerator | Labelersubject, user: Profileuser }) => {
if (subject: Post | FeedGenerator | Labelersubject instanceof class Post
Represents a post on Bluesky.
Post
) {
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
@seesource
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.
@sincev0.1.100
log
(subject: Postsubject.PostReference.uri: string
The post's AT URI.
uri
);
} });

Now, whenever the bot receives a like, it will first ensure that the like is for a post (feed generators and the labeler profile can also receive likes!) and then log the AT URI of the post to the console.

Of course, that’s not very useful. Let’s start applying labels based on which post the user liked. We’re going to create an object mapping the AT URIs of the posts we created earlier to the labels we want to apply, then use that object to label the user’s account.

const const postsToLabels: Record<string, string>postsToLabels: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, string> = {
"at://did:.../app.bsky.feed.post/f1234": "fire", "at://did:.../app.bsky.feed.post/w3456": "water", "at://did:.../app.bsky.feed.post/a5678": "air", "at://did:.../app.bsky.feed.post/e7890": "earth", } const bot: Botbot.Bot.on(event: "like", listener: (event: { subject: Post | FeedGenerator | Labeler; user: Profile; uri: string; }) => void): Bot (+9 overloads)
Emitted when one of the bot's posts is liked.
@paramlistener A callback function that receives the subject that was liked, the user who liked it, and the like's AT URI.
on
("like", async ({ subject: Post | FeedGenerator | Labelersubject, user: Profileuser }) => {
if (subject: Post | FeedGenerator | Labelersubject instanceof class Post
Represents a post on Bluesky.
Post
) {
const const label: stringlabel = const postsToLabels: Record<string, string>postsToLabels[subject: Postsubject.PostReference.uri: string
The post's AT URI.
uri
];
if (const label: stringlabel) { await user: Profileuser.Profile.labelAccount(labels: Array<string>, comment?: string): Promise<void>
Apply labels to the user's account. Note that this will label the user's profile and all posts they create! If you only want to label their profile, use the {@link labelProfile } method.
@paramlabels The labels to apply.@paramcomment An optional comment to attach to the label.
labelAccount
([const label: stringlabel]);
} } });

Make sure to substitute in the right AT URIs from earlier. We’re using the Profile#labelAccount method to label the user’s account, so that the label will appear on both their profile and posts.

Your final code should look something like this:

import { class LabelerServerLabelerServer } from "@skyware/labeler";
import { class Bot
A bot that can interact with the Bluesky API.
Bot
, class Post
Represents a post on Bluesky.
Post
} from "@skyware/bot";
const const server: LabelerServerserver = new new LabelerServer(options: LabelerOptions): LabelerServer
Create a labeler server.
@paramoptions Configuration options.
LabelerServer
({
LabelerOptions.did: string
The DID of the labeler account.
did
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_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.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringSIGNING_KEY,
}); const server: LabelerServerserver.LabelerServer.start(port: number, callback?: (error: Error | null, address: string) => void): void
Start the server.
@paramport The port to listen on.@paramcallback A callback to run when the server is started.
start
(14831, (error: Error | nullerror) => {
if (error: Error | nullerror) { 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
@seesource
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.
@sincev0.1.100
error
("Failed to start: ", error: Errorerror);
} 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
@seesource
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.
@sincev0.1.100
log
("Listening on port 14831");
} }); const const bot: Botbot = new new Bot({ service, langs, emitEvents, emitChatEvents, rateLimitOptions, cacheOptions, eventEmitterOptions, }?: BotOptions): Bot
Create a new bot.
@paramoptions Configuration options.
Bot
();
await const bot: Botbot.Bot.login({ identifier, password }: BotLoginOptions): Promise<OutputSchema>
Log in with an identifier and password.
@paramoptions The bot account's identifier and password.@returnsSession data.
login
({
BotLoginOptions.identifier: string
The bot account's email, handle, or DID.
identifier
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_DID,
BotLoginOptions.password: string
The bot account's password.
password
: var process: NodeJS.Processprocess.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"' &#x26;&#x26; 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.
@sincev0.1.27
env
.stringLABELER_PASSWORD,
}); const const postsToLabels: Record<string, string>postsToLabels: type Record<K extends keyof any, T> = { [P in K]: T; }
Construct a type with a set of properties K of type T
Record
<string, string> = {
"at://did:.../app.bsky.feed.post/f1234": "fire", "at://did:.../app.bsky.feed.post/w3456": "water", "at://did:.../app.bsky.feed.post/a5678": "air", "at://did:.../app.bsky.feed.post/e7890": "earth", } const bot: Botbot.Bot.on(event: "like", listener: (event: { subject: Post | FeedGenerator | Labeler; user: Profile; uri: string; }) => void): Bot (+9 overloads)
Emitted when one of the bot's posts is liked.
@paramlistener A callback function that receives the subject that was liked, the user who liked it, and the like's AT URI.
on
("like", async ({ subject: Post | FeedGenerator | Labelersubject, user: Profileuser }) => {
if (subject: Post | FeedGenerator | Labelersubject instanceof class Post
Represents a post on Bluesky.
Post
) {
const const label: stringlabel = const postsToLabels: Record<string, string>postsToLabels[subject: Postsubject.PostReference.uri: string
The post's AT URI.
uri
];
if (const label: stringlabel) { await user: Profileuser.Profile.labelAccount(labels: Array<string>, comment?: string): Promise<void>
Apply labels to the user's account. Note that this will label the user's profile and all posts they create! If you only want to label their profile, use the {@link labelProfile } method.
@paramlabels The labels to apply.@paramcomment An optional comment to attach to the label.
labelAccount
([const label: stringlabel]);
} } });

All that’s left is to log into another account, like a post or multiple, and see the labels appear on your profile! (Make sure you’ve subscribed to the labeler and set the labels you want to see to “Show badge”!)

A screenshot from a user's profile of the "Air" label applied, with its description below and an "Appeal" button to the right.