npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@4players/odin-bot-sdk

v0.4.4

Published

A simple SDK built on top of Odin NodeJS SDK and Odin Foundation to quickly build simple bots that welcome users, change rooms or adding AI bots to Odin based text chats.

Downloads

273

Readme

ODIN Bot SDK for NodeJS

This is a simple bot SDK built on top of the ODIN Web (JS/TS) SDK to make it easier to build bots for ODIN. It provides a simple interface to the ODIN Web SDK and handles the communication with the ODIN Web SDK.

Prerequisites

ODIN in its core is super flexiblen and allows you to build and use any data structures that you like. However, this flexibility comes with a price: It makes interacting between different applications and bots a bit more difficult. For this we built a set of simple core data structures bundled in the @4players/odin-foundation package. It contains interfaces for users and messages.

The ODIN Bot SDKs uses these data structures to build internal user representations and to send messages and RPCs.

If you want your application to interact with the Bot SDK make sure to use the same naving conventions as defined in the ODIN foundation.

If you have already built your own data structures, you can still make use of the Bot SDK. Just make a few adjustments to the OdinBot class to create data structures your application needs.

Installation

Add the bot SDK to your NodeJS project:

npm install @4players/odin-bot-sdk

Usage

You need to create you own bot class that extends the OdinBot class. You can then add your own functionality by overriding a few functions:

import {
    OdinMessageReceivedEventPayload,
    OdinPeerJoinedEventPayload,
    OdinPeerLeftEventPayload
} from "@4players/odin-nodejs";

class MyBot extends OdinBot {

    /** Override register RPC methods to register your own RPC methods */
    protected override registerRPCMethods() {
        this.registerRPCMethod("getUsers");
        this.registerRPCMethod("getPing");
    }

    /** Override onPeerLeft to get notified when a new user left the room */
    protected override async onPeerLeft(event: OdinPeerLeftEventPayload, user: IUser) {
        await this.sendTextMessage(`${user.name} left the room`);
    }

    /** Override onPeerJoined to get notified when a new user joined the room */
    protected override async onPeerJoined(event: OdinPeerJoinedEventPayload, user: IUser) {
        if (this.isJoined) {
            // Send a private text message to the individual user (this message cannot be seen by other peers in the room
            await this.sendTextMessage(`Hello there ${user.name}! I am a bot and will store all messages that you 
            write so other can also read them. If you are not happy with that, please leave this channel`, event.peerId);
            // Send a public text message to all users in the room
            await this.sendTextMessage(`${user.name} joined the room`);
        }
    }

    /** Override onTextMessageReceived to get notified when a new text message was received */
    protected override async onTextMessageReceived(event: OdinMessageReceivedEventPayload, message: IChatMessage, userData: IUserData) {
        console.log("Someone sent this message:", message, userData);
    }

    /** One of your own RPC methods */
    protected async getUsers(senderPeerId: number) {
        await this.sendTextMessage(`There are ${this.users.size} users in this room`, senderPeerId);
    }

    /** Another RPC method */
    protected async getPing(senderPeerId: number, text: string) {
        await this.sendTextMessage(`We got this text: ${text}`, senderPeerId);
    }
}

Next, create an instance of the class by providing your access key and a unique bot id. This allows your application to distinguish between different bots. Finally, start the bot by providing the name of the room.


const main = async function() {
  const bot = new MyBot("Ad4R7/hpCx1U5yGvC61oNBeJ/fWiW7dodvXWW7MEwrjg", "bot-00001");
  
  // Set the sample rate and number of channels to use for audio capture (default is 48000 and 1)
  const sampleRate = 48000;
  const numChannels = 1;
  
  await bot.start("Lobby", sampleRate, numChannels);
}

main()
  .then(() => {
    console.log("Bot started");
  })
  .catch((err) => {
    console.error("Could not start bot", err);
  });

Please note: The start function provides

Functions you can override

You can override these functions of the OdinBot class to adapt functionality:

  • protected override registerRPCMethods(): Called when the bot is started. Use this to register your own RPC methods
  • protected override async onPeerLeft(event: OdinPeerLeftEventPayload, user: IUser): Called when a new user joined the room
  • protected override async onPeerJoined(event: OdinPeerJoinedEventPayload, user: IUser): Called when a user left the room
  • protected override async onTextMessageReceived(event: OdinMessageReceivedEventPayload, message: IChatMessage, userData: IUserData): Called when a new text message was received
  • protected override async onUserDataChanged(event: OdinPeerUserDataChangedEventPayload, userData: IUserData): Called when the user data of a user changed
  • protected override async onRoomDataChanged(event: OdinRoomUserDataChangedEventPayload, roomData: IRoomData): Called when the room data changed
  • protected override async onRoomJoined(event: OdinJoinedEventPayload, roomData: IRoomData): Called when the bot joined a room
  • protected override async onRoomLeft(event: OdinLeftEventPayload): Called when the bot left a room
  • protected override onMediaActivity(event: OdinMediaActivityEventPayload, user: IUser, active: boolean): Called when the media activity of a user changed
  • protected override onMediaAdded(event: OdinMediaAddedEventPayload, user: IUser, mediaId: number): Called when a user added a new media stream
  • protected override onMediaRemoved(event: OdinMediaRemovedEventPayload, user: IUser, mediaId: number): Called when a user removed a media stream
  • protected override onAudioDataReceived(event: OdinAudioDataReceivedEventPayload, user: IUser): Called when new audio samples are available

Capturing audio

With the new Bot SDK (from version 0.2.0) you can record audio from each individual user. To do so, you need to start capturing audio:

    this.startCaptureAudio();

If you want to capture audio right from the beginning, use the onBeforeJoin callback function to start capturing:

class MyBot extends OdinBot {
    //  ...
    protected override onBeforeJoin() {
        this.startCaptureAudio();
    }
    //  ...
}

Please note: Users typically don't expect to be recorded. So you should inform your users about that by sending a message or by showing a popup.

You will now receive onAudioDataReceived events for each user that is talking every 20 milliseconds. In the event payload you'll receive 16-bit samples ranging from -32768 to 32767 or 32-bit floats ranging from -1 to 1. The sample rate is determined by the sample rate and channel count that you set when starting the bot. Different audio libraries handle audio differently and require different samples. You should be fine with either the 16-bit or 32-bit samples.

This is a simple example of how to record audio using the wav NPM package:

class MyBot extends OdinBot {
    private fileRecorder: wav.FileWriter;
    //  ...
    protected override onBeforeJoin() {
        // Start capture audio (required to receive audio data)
        this.startCaptureAudio();
        
        this.fileRecorder = new wav.FileWriter("audio.wav", {
            channels: 1,
            sampleRate: 48000,
            bitDepth: 16
        });
    }

    protected override async onAudioDataReceived(event: OdinAudioDataReceivedEventPayload, user: IUser) {
        // You can directly write the 16 bit samples to the WAV encoder
        this.fileRecorder.wavEncoder.file.write(event.samples16, (error) => {
            if (error) {
                console.log("Failed to write audio file");
            }
        });
    }
    //  ...
}

As you can see, it's super simple to receive audio data and working with them. In our example (see /examples folder) we show you how to leverage OpenAI to transcribe audio. As you receive individual audio files for each user, you can also use that for moderation purposes.

Sending audio

With the new Bot SDK (from version 0.2.0) you can also send audio to the room. To do so, you need to create an OdinMedia instance like this:

// Create a new audio stream with 44.1 kHz and 1 channel
const media = room.createAudioStream(44200, 1);

// Prepare our MP3 decoder and load the sample file
const audioBuffer = await decode(fs.readFileSync('./santa.mp3'));

// Create a stream that will match the settings of the file
const audioBufferStream = new AudioBufferStream({channels: 1, sampleRate: 44100, float: true, bitDepth: 32});

// Whenever the stream has data, send it to the media stream
audioBufferStream.on('data', (data) => {
    const floats = new Float32Array(new Uint8Array(data).buffer)
    media.sendAudioData(floats);
});

// Write the audio file to the stream. AudioBufferStream will read the file and send it as a media stream which will
// trigger the on('data') event which we use to just forward the samples to ODIN
audioBufferStream.write(audioBuffer);
}

We used the npm packages audio-buffer-stream and audio-decode for this example code. Of course there are other ways to handle audio. It's just important to regularly (every 20 milliseconds) send audio data to the media stream. ODIN requires 32-bit floats with values ranging from -1 to 1. The sample rate and channel count must match the options used when creating the media object.

Samples

You can find a sample bot in the examples folder. It's a simple bot that will send a message to the room when a user joins or leaves and answers questions starting with @bot using ChatGPT-API from OpenAI. It also records audio and transcribes them using OpenAIs new whisper model.

The @4players/odin-nodejs package also contains some working samples for recording and playing audio.

RPC methods

The @4players/odin-foundation packages provides data structures for different kind of messages:

  • message: A simple text message
  • poke: A text message that notifies the user
  • rpc : A remote procedure call that can be used to trigger actions in the bot

You need to register a member function of your class as a RPC method by calling the registerRPCMethod function. Best place for that is to override the registerRPCMethods function.

class MyBot extends OdinBot {
  //  ...
  protected override registerRPCMethods() {
    this.registerRPCMethod("getUsers");
    this.registerRPCMethod("getPing");
  }
  //  ...
}

RPC methods need to have this format:

export type OdinBotRPCMethod = (senderPeerId: number, ...args: any[]) => void;

The first parameter is the peer id of the sender (of the rpc call) and optionally additional parameters.

To send the RPC call from your application, you only need to send a message with this data structure to the bot. The bot will then call the registered RPC method.

const rpcPayload: IRPCPayload = {
  method: 'getPing',
  args: ['This is my text and expect the bot to send it back']
}

const message: IMessageTransferFormat = {
  kind: 'rpc',
  payload: rpcPayload
}

await this.room.sendMessage(this.encodeObjToUint8Array(message));