Chatting with Users

Not all conversations are meant to be public. You can create a chat conversation to interact with users in private.

Note: Your bot should never interact with users without obtaining their consent. Be extra careful to avoid potentially spammy behaviour in private conversations!

Accepting Messages

By default, your bot account will only receive messages from users it follows. To receive messages from any (or no) user, you can use the Bot#setChatPreference method.

Valid options for this method are:

const const bot: Botbot = new new Bot({ service, langs, emitEvents, emitChatEvents, rateLimitOptions, cacheOptions, eventEmitterOptions, }?: BotOptions | undefined): Bot
Create a new bot.
@paramoptions Configuration options.
Bot
();
await const bot: Botbot.Bot.setChatPreference(preference: IncomingChatPreference): Promise<void>
Set the bot's preference for who can initiate a new chat conversation. This does not affect existing conversations.
@parampreference The new preference.
setChatPreference
(const IncomingChatPreference: { readonly All: "all"; readonly None: "none"; readonly Following: "following"; }
A user's preference for who can initiate a chat conversation.
@enum
IncomingChatPreference
.type All: "all"
Receive messages from everyone.
All
);

This only needs to be set once for your bot account. You can change it at any time.

Starting a Conversation

To chat with a user, you need a Conversation object.

If you know the DID(s) of the user(s) you want to chat with, you can use the Bot#getConversationForMembers method.

const const conversation: Conversationconversation = await const bot: Botbot.Bot.getConversationForMembers(members: string[], options?: BaseBotGetMethodOptions | undefined): Promise<Conversation>
Fetch a conversation containing 1-10 members. If a conversation doesn't exist, it will be created.
@parammembers The DIDs of the conversation members.@paramoptions Optional configuration.
getConversationForMembers
(["did:plc:ith6w2xyj2qy3rcvmlsem6fz"]);

This method will fetch an existing conversation if one exists, or create a new one if it doesn’t.

You can obtain a list of all conversations the bot is a part of by calling the Bot#listConversations method.

const { const cursor: string | undefinedcursor, const conversations: Conversation[]conversations } = await const bot: Botbot.Bot.listConversations(options?: BotListConversationsOptions | undefined): Promise<{
    cursor: string | undefined;
    conversations: Conversation[];
}>
Fetch all conversations the bot is a member of.
@paramoptions Optional configuration.
listConversations
();
// The method only returns 100 conversations at a time — use the cursor to fetch more conversations const { conversations: Conversation[]conversations: const moreConversations: Conversation[]moreConversations } = await const bot: Botbot.Bot.listConversations(options?: BotListConversationsOptions | undefined): Promise<{ cursor: string | undefined; conversations: Conversation[]; }>
Fetch all conversations the bot is a member of.
@paramoptions Optional configuration.
listConversations
({ BotListConversationsOptions.cursor?: string | undefined
The offset at which to start fetching conversations.
cursor
});

Sending and Reading Messages

Start by obtaining the Conversation object for the user you want to chat with. You can then send messages to the user and read their responses.

const const conversation: Conversationconversation = await const bot: Botbot.Bot.getConversationForMembers(members: string[], options?: BaseBotGetMethodOptions | undefined): Promise<Conversation>
Fetch a conversation containing 1-10 members. If a conversation doesn't exist, it will be created.
@parammembers The DIDs of the conversation members.@paramoptions Optional configuration.
getConversationForMembers
(["skyware.js.org"]);

You can read chat history by calling the Conversation#getMessages method. This will return an array of ChatMessage and DeletedChatMessage objects.

const { const cursor: string | undefinedcursor, const messages: (ChatMessage | DeletedChatMessage)[]messages } = await const conversation: Conversationconversation.Conversation.getMessages(cursor?: string | undefined): Promise<{
    cursor: string | undefined;
    messages: (ChatMessage | DeletedChatMessage)[];
}>
Fetch a list of messages in this conversation. This method returns 100 messages at a time, beginning from the latest message, alongside a cursor to fetch the next 100.
@paramcursor The cursor to begin fetching from.@returnsAn array of messages and a cursor for pagination.
getMessages
();
// You'll only receive up to 100 messages at a time — you can use the cursor to fetch messages further into the past! const { messages: (ChatMessage | DeletedChatMessage)[]messages: const moreMessages: (ChatMessage | DeletedChatMessage)[]moreMessages } = await const conversation: Conversationconversation.Conversation.getMessages(cursor?: string | undefined): Promise<{ cursor: string | undefined; messages: (ChatMessage | DeletedChatMessage)[]; }>
Fetch a list of messages in this conversation. This method returns 100 messages at a time, beginning from the latest message, alongside a cursor to fetch the next 100.
@paramcursor The cursor to begin fetching from.@returnsAn array of messages and a cursor for pagination.
getMessages
(const cursor: string | undefinedcursor);

You can send messages to the user by calling the Conversation#sendMessage method.

await const conversation: Conversationconversation.Conversation.sendMessage(payload: Omit<ChatMessagePayload, "conversationId">, options?: BotSendMessageOptions | undefined): Promise<ChatMessage>
Send a message in the conversation.
@parampayload The message to send.@paramoptions Additional options for sending the message.@returnsThe sent message.
sendMessage
({ text: string | RichText
The message text. Can be a string or a RichText instance containing facets.
text
: "Hello, world!" });

Like posts, you can use a RichText instance to send rich text messages containing links and mentions. More on that in Rich Text.

import { class RichText
Used to build a rich text string with facets.
RichText
} from "@skyware/bot";
await const conversation: Conversationconversation.Conversation.sendMessage(payload: Omit<ChatMessagePayload, "conversationId">, options?: BotSendMessageOptions | undefined): Promise<ChatMessage>
Send a message in the conversation.
@parampayload The message to send.@paramoptions Additional options for sending the message.@returnsThe sent message.
sendMessage
({
text: string | RichText
The message text. Can be a string or a RichText instance containing facets.
text
: new new RichText(): RichText
Used to build a rich text string with facets.
RichText
().RichText.text(substr: string): RichText
Append a string.
text
("I love ").RichText.link(substr: string, uri?: string | undefined): RichText
Append a link. You can use the uri parameter to specify a different URI than the substr.
@exampleCreate a hyperlink new RichText().text("Go to ").link("https://bsky.app").build();@exampleCreate a hyperlink with custom text new RichText().text("Go to ").link("Bluesky", "https://bsky.app").build();
link
("Skyware", "https://skyware.js.org").RichText.text(substr: string): RichText
Append a string.
text
("!")
});

If you already have a Profile object, you can skip the Conversation object and send and read messages directly.

await const profile: Profileprofile.Profile.sendMessage(payload: Omit<ChatMessagePayload, "conversationId">): Promise<ChatMessage>
Send the user a direct message.
@parampayload The message to send.
sendMessage
({ text: string | RichText
The message text. Can be a string or a RichText instance containing facets.
text
: "Hello, world!" });
const { const messages: (ChatMessage | DeletedChatMessage)[]messages } = await const profile: Profileprofile.Profile.getMessages(cursor?: string | undefined): Promise<{ cursor: string | undefined; messages: (ChatMessage | DeletedChatMessage)[]; }>
Fetch the message history between the bot and this user.
@paramcursor The cursor to begin fetching from.@returnsAn array of messages and a cursor for pagination.
getMessages
();

Receiving Message Events

Of course, you won’t usually know in advance who you want to message. You may want your bot to respond to private messages from any user.

To start, you’ll want to initialize your bot with emitChatEvents set to true.

const const bot: Botbot = new new Bot({ service, langs, emitEvents, emitChatEvents, rateLimitOptions, cacheOptions, eventEmitterOptions, }?: BotOptions | undefined): Bot
Create a new bot.
@paramoptions Configuration options.
Bot
({ BotOptions.emitChatEvents?: boolean | undefined
Whether to emit chatMessage events (this is independent of {@link emitEvents } ).
@defaultfalse
emitChatEvents
: true });

Your bot will now emit an event whenever a message is received, which you can handle as follows:

const bot: Botbot.Bot.on(event: "message", listener: (message: ChatMessage) => void): Bot (+9 overloads)
Emitted when the bot receives a message in a DM conversation.
@paramlistener A callback function that receives the message.
on
("message", async (message: ChatMessagemessage: class ChatMessage
Represents a message in a chat conversation.
ChatMessage
) => {
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
(`Received message: ${message: ChatMessagemessage.ChatMessage.text: string
The message's text.
text
}`);
});

You can access a reference to the sender’s profile and the conversation the message was sent in by calling the getSender and getConversation methods.

const bot: Botbot.Bot.on(event: "message", listener: (message: ChatMessage) => void): Bot (+9 overloads)
Emitted when the bot receives a message in a DM conversation.
@paramlistener A callback function that receives the message.
on
("message", async (message: ChatMessagemessage: class ChatMessage
Represents a message in a chat conversation.
ChatMessage
) => {
const const sender: Profilesender = await message: ChatMessagemessage.DeletedChatMessage.getSender(): Promise<Profile>
Fetch the profile of the user who sent this message.
getSender
();
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
(`Received message from @${const sender: Profilesender.Profile.handle: string
The user's handle.
handle
}: ${message: ChatMessagemessage.ChatMessage.text: string
The message's text.
text
}`);
const const conversation: Conversation | nullconversation = await message: ChatMessagemessage.DeletedChatMessage.getConversation(): Promise<Conversation | null>
Fetch the Conversation instance this message belongs to. Returns null if the conversation could not be found.
getConversation
();
if (const conversation: Conversation | nullconversation) { // It may not always be possible to resolve the conversation the message was sent in! await const conversation: Conversationconversation.Conversation.sendMessage(payload: Omit<ChatMessagePayload, "conversationId">, options?: BotSendMessageOptions | undefined): Promise<ChatMessage>
Send a message in the conversation.
@parampayload The message to send.@paramoptions Additional options for sending the message.@returnsThe sent message.
sendMessage
({ text: string | RichText
The message text. Can be a string or a RichText instance containing facets.
text
: "Hey there, " + const sender: Profilesender.Profile.displayName?: string | undefined
The user's display name.
displayName
+ "!" });
} });

With Great Power…

…comes great responsibility. People don’t like unsolicited messages. It’s your responsibility to ensure your bot is well-behaved and makes Bluesky a better place for everyone.