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();