---
title: "Chat and Commands"
description: "Party chat, whispers, friends, and local commands."
canonical: "https://developers.fnlb.net/docs/sdk/chat-and-commands"
markdown: "/docs/sdk/chat-and-commands.md"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://developers.fnlb.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat and Commands

---
title: "Chat and commands"
sidebarTitle: "Chat and commands"
description: "Send party chat and whispers, manage Epic friends, and run a command on the local shard."
icon: "terminal"
---

These calls go to the shard that owns the bot. The bot has to be running in this process and past [Events.Ready](/docs/sdk/events.md#ready). For a bot that is only online in the cloud, use the [HTTP client](/docs/sdk/http-client.md).

## Party chat and whispers

```ts
await bot.send('hello party');
await bot.whisper('epic-account-id', 'hello');
await bot.party.send('same as bot.send');
```

`whisper` takes an Epic account id or display name. The recipient has to be a friend.

Incoming chat is an event, not a poll. `message.reply` uses the same channel the message arrived on.

```ts
fnlb.on(Events.PartyMemberMessage, async (message) => {
  await message.reply(`you said ${message.content}`);
});

fnlb.on(Events.FriendMessage, async (message) => {
  await message.reply('got your whisper');
});
```

## Commands

`bot.runCommand` runs a bot command as the account owner. The name has no prefix. This does not go through the cloud API.

```ts
const reply = await bot.runCommand('outfit', ['renegade']);
console.log(reply.content, reply.success);

await bot.runCommand('emote', 'wave');
await bot.runCommand('emote', ['wave'], 'en');
```

`args` is a string or a string array. `locale` defaults to the bot's locale. The result is a [`SdkCommandResult`](/sdk?symbol=SdkCommandResult):

| Field | Meaning |
| :--- | :--- |
| `content` | Text the command would have sent back. |
| `success` | False when the command replied with an error. |
| `format` | 0 neutral, 1 success, 2 info, 3 warn, 4 error. |
| `botName` | The bot's FNLB nickname. |

Cosmetic changes you want as a direct call, without a command reply, are `party.setOutfit` and the other member methods in [Bots and parties](/docs/sdk/parties.md#lobby-controls). Use `runCommand` when you want the same path as a chat command, including its checks and reply text.

The same command on a bot that is not in this process is `fnlb.api.bots.commands.run`. See [HTTP client](/docs/sdk/http-client.md#commands-and-chat). The cloud result uses [`ApiCommandResult`](/sdk?symbol=ApiCommandResult), which has the same `content` and `format` fields.

```ts
fnlb.on(Events.PartyMemberMessage, async (message) => {
  if (!message.content.startsWith('!outfit ')) return;
  const name = message.content.slice('!outfit '.length);
  const reply = await message.bot.runCommand('outfit', [name]);
  await message.reply(reply.content);
});
```

## Friends

```ts
const { onlineFriends, offlineFriends } = await bot.friends();
const { incomingFriends, outgoingFriends } = await bot.pendingFriends();

await bot.acceptFriend(incomingFriends[0].id);
await bot.declineFriend('epic-account-id');
await bot.addFriend('DisplayName');
await bot.removeFriend('epic-account-id');
await bot.block('epic-account-id');
await bot.unblock('epic-account-id');

const joinable = onlineFriends.filter((friend) => friend.isJoinable);
```

`friends()` returns [`ApiFriends`](/sdk?symbol=ApiFriends): `onlineFriends` and `offlineFriends`. An online friend has `id`, `displayName`, `isJoinable`, and `status`. `pendingFriends()` returns [`ApiPendingFriends`](/sdk?symbol=ApiPendingFriends): `incomingFriends` and `outgoingFriends`.

`acceptFriend` and `declineFriend` take the Epic account id from that list. `declineFriend` also cancels an outgoing request. `addFriend` and `removeFriend` take an Epic account id or display name. `block` and `unblock` use the same id. `unblock` only works for an account that is already on the block list. `removeFriend` also drops a pending request.

`Events.FriendRequest` fires when someone sends a request, so you do not have to poll `pendingFriends()` for that case. See [Events](/docs/sdk/events.md#friends). The HTTP routes `api.bots.friends.list` and `api.bots.users.blocked` read the same kind of data for a bot that is connected in the cloud. They do not add, remove, or block. Those writes are the local methods above.

## Reference

| Call | Symbol |
| :--- | :--- |
| Party chat | [`Bot.send`](/sdk?symbol=Bot#send) |
| Whisper | [`Bot.whisper`](/sdk?symbol=Bot#whisper) |
| Reply | [`Message.reply`](/sdk?symbol=Message#reply) |
| Local command | [`Bot.runCommand`](/sdk?symbol=Bot#runcommand) |
| Add a friend | [`Bot.addFriend`](/sdk?symbol=Bot#addfriend) |
| Block | [`Bot.block`](/sdk?symbol=Bot#block) |
