Skip to main content

Discord bot

Find example source code here.

1. Import dependencies and initialize Discord and Redis clients:

import {
InworldClient,
InworldPacket,
ServiceError,
SessionToken,
status,
} from '@inworld/nodejs-sdk';
import {
Client as DiscordClient,
DMChannel,
GatewayIntentBits,
Message,
Partials,
} from 'discord.js';
import { createClient as createRedisClient } from 'redis';

const redisClient = createRedisClient();
const discordClient = new DiscordClient({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.DirectMessageTyping,
GatewayIntentBits.DirectMessageReactions,
],
partials: [Partials.Channel],
});

2. Use channel id and author id as key for conversation.

const getKey = (message: Message) =>
`${message.channel.id}_${message.author.id}`;

3. Define Redis storage class.

import { Session } from '@inworld/nodejs-sdk';
import { createClient } from 'redis';

class Storage {
private redisClient = createClient();

async connect({ onError }: { onError?: (err: Error) => void }) {
await this.redisClient.connect();

if (onError) {
this.redisClient.on('error', onError);
}
}

disconnect() {
this.redisClient.disconnect();
}

async get(key: string) {
const json = await this.redisClient.get(key);

return Session.deserialize(json);
}

set(key: string, entity: Session) {
this.redisClient.set(key, Session.serialize(entity));
}

delete(key: string) {
this.redisClient.del(key);
}
}

4. Create Inworld Client.

const key = 'channel_user_key';
const createInworldClient = async (props: {
message: Message;
direct?: boolean;
}) => {
const { message, direct } = props;

const key = getKey(message);
const client = new InworldClient()
.setOnSession({
get: () => storage.get(key),
set: (session: Session) => storage.set(key, session),
})
.setApiKey({
key: process.env.INWORLD_KEY!,
secret: process.env.INWORLD_SECRET!,
})
.setConfiguration({
capabilities: { audio: false },
// Additional protection to multiple long sessions.
...(!direct && { connection: { disconnectTimeout: 5 * 1000 } }),
})
.setScene(process.env.INWORLD_SCENE!)
.setOnError(handleError(message))
.setOnMessage((packet: InworldPacket) => {
if (!direct && packet.isInteractionEnd()) {
client.close();
return;
}

if (packet.isText() && packet.text.final) {
message.channel.send(packet.text.text);
}
})
.build();

return client;
};

5. Send user message to character and then print answer into channel chat.

const sendMessage = async (message: Message, direct?: boolean) => {
const content = message.content.replace(`<@${discordClient.user?.id}>`, '');

const client = await createInworldClient({ direct, message });

client.sendText(content);
};

6. Handle errors.

const handleError = (message: Message, direct?: boolean) => {
return (err: ServiceError) => {
switch (err.code) {
// Skip server and client side disconnect events.
case status.ABORTED:
case status.CANCELLED:
break;
// It's impossible to reuse provided session id.
// Try to get new one token.
case status.FAILED_PRECONDITION:
redisClient.del(getKey(message));
sendMessage(message, direct);
break;
// Other errors.
default:
console.error(`Error: ${err.message}`);
break;
}
};
}};

7. Run application: connect to Redis, connect to Discord, send user message to character.

const run = async function () {
await storage.connect({
onError: (err: Error) => console.log('Redis Client Error', err),
});

discordClient.on('ready', () => {
console.log("I'm ready!");
});

discordClient.on('messageCreate', async (message: Message) => {
if (message.author.bot) return;

if (message.channel instanceof DMChannel) {
sendMessage(message, true);
} else if (discordClient.user && message.mentions.has(discordClient.user)) {
sendMessage(message);
}
});

discordClient.login(process.env.DISCORD_BOT_TOKEN);
};

run();