Creating a Simple Chat Bot

For this tutorial, we will show you how to create a 'ping-pong' chat bot using either our Node, Java or Python libraries to communicate with the Mixer API. We'll also take a look at how you can talk to the API directly with curl and wscat.

We'll be using OAuth for authentication. In the tutorial code below, click "Click here to get your token" to grab a token for the tutorial. You can read more about how OAuth works on our OAuth reference page

Prerequisites

  1. Get NodeJS and NPM for your platform.
  2. Create a new project with npm.
  3. Run npm i -S beam-client-node

Writing the Code

Our Node implementation uses Bluebird promises, you can find out more about them here. For this tutorial we'll also be using some ES2015 concepts. If you're new to ES2015 there's a great guide here on the changes.

Before we can connect to the chat servers, we must authenticate ourselves with the backend. In our example we are going to use an implicit OAuth token for authentication. The required scopes are chat:connect chat:chat.

const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');

let userInfo;

const client = new BeamClient();

// With OAuth we don't need to login, the OAuth Provider will attach
// the required information to all of our requests after this call.
client.use('oauth', {
    tokens: {
        access: 'Click here to get your Token!',
        expires: Date.now() + (365 * 24 * 60 * 60 * 1000)
    },
});

// Get's the user we have access to with the token
client.request('GET', `users/current`)
.then(response => {
    console.log(response.body);
    // Store the logged in user's details for later refernece
    userInfo = response.body;
    // Returns a promise that resolves with our chat connection details.
    return client.chat.join(response.body.channel.id);
})
.then(response => {
    const body = response.body;
    console.log(body);
    // TODO: Connect to chat.
})
.catch(error => {
    console.log('Something went wrong:', error);
});

The result will look something like this in it you can see an array of chat servers that we can connect to within the endpoint array.:

{ endpoints:
   [ 'wss://chat1-dal.mixer.com:443',
     'wss://chat2-dal.mixer.com:443' ],
  authkey: '1c0e251998ac7112f42c71a23d4b67b3',
  permissions:
   [ 'change_ban',
     'edit_options',
     'change_role',
     'bypass_links',
     'bypass_slowchat',
     'remove_message',
     'clear_messages',
     'timeout',
     'giveaway_start',
     'poll_vote',
     'poll_start',
     'connect',
     'chat' ] }

Next we will use this response to connect to the chat server, Let's write a createChatSocket() function and connect to chat.

const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');

let userInfo;

const client = new BeamClient();

// With OAuth we don't need to login, the OAuth Provider will attach
// the required information to all of our requests after this call.
client.use('oauth', {
    tokens: {
        access: 'Click here to get your Token!',
        expires: Date.now() + (365 * 24 * 60 * 60 * 1000)
    },
});

// Get's the user we have access to with the token
client.request('GET', `users/current`)
.then(response => {
    console.log(response.body);
    // Store the logged in user's details for later refernece
    userInfo = response.body;
    // Returns a promise that resolves with our chat connection details.
    return client.chat.join(response.body.channel.id);
})
.then(response => {
    const body = response.body;
    console.log(body);
    return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey);
})
.catch(error => {
    console.log('Something went wrong:', error);
});


/**
 * Creates a beam chat socket and sets up listeners to various chat events.
 * @param {number} userId The user to authenticate as
 * @param {number} channelId The channel id to join
 * @param {any} endpoints An endpoints array from a beam.chat.join call.
 * @param {any} authkey An authentication key from a beam.chat.join call.
 * @returns {Promise.<>}
 */
function createChatSocket (userId, channelId, endpoints, authkey) {
    const socket = new BeamSocket(endpoints).boot();

    // You don't need to wait for the socket to connect before calling methods,
    // we spool them and run them when connected automatically!
    socket.auth(channelId, userId, authkey)
    .then(() => {
        console.log('You are now authenticated!');
        // Send a chat message
        return socket.call('msg', ['Hello world!']);
    })
    .catch(error => {
        console.log('Oh no! An error occurred!', error);
    });

    // Listen to chat messages, note that you will also receive your own!
    socket.on('ChatMessage', data => {
        console.log('We got a ChatMessage packet!');
        console.log(data);
        console.log(data.message); // lets take a closer look
    });

    // Listen to socket errors, you'll need to handle these!
    socket.on('error', error => {
        console.error('Socket error', error);
    });
}

Running this code will now connect you to chat and you'll see something like this in the console. The JSON object on the 3rd line is the chat message packet your bot sent to the server when it connected.

You are now authenticated!
We got a ChatMessage packet!
{ channel: 131630,
  id: '9bc8a940-326a-11e6-9af9-8d8f189ce625',
  user_name: 'your_username',
  user_id: <your_userid>,
  user_roles: [ 'Owner' ],
  message: { message: [ [Object] ], meta: {} } }
{ message: [ { type: 'text', data: 'Hello world!', text: 'Hello world!' } ],
  meta: {} }

Now that we have a chat connection with authentication working we can add the rest of the bot code. When a user joins the channel it will greet them. If a user types !ping into chat then it will response with their name and "PONG!". To do this we'll re-write parts of the createChatSocket() function. The final code is below, check it out!

const BeamClient = require('beam-client-node');
const BeamSocket = require('beam-client-node/lib/ws');

let userInfo;

const client = new BeamClient();

// With OAuth we don't need to login, the OAuth Provider will attach
// the required information to all of our requests after this call.
client.use('oauth', {
    tokens: {
        access: 'Click here to get your Token!',
        expires: Date.now() + (365 * 24 * 60 * 60 * 1000)
    },
});

// Get's the user we have access to with the token
client.request('GET', `users/current`)
.then(response => {
    userInfo = response.body;
    return client.chat.join(response.body.channel.id);
})
.then(response => {
    const body = response.body;
    return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey);
})
.catch(error => {
    console.log('Something went wrong:', error);
});

/**
 * Creates a beam chat socket and sets up listeners to various chat events.
 * @param {number} userId The user to authenticate as
 * @param {number} channelId The channel id to join
 * @param {any} endpoints An endpoints array from a beam.chat.join call.
 * @param {any} authkey An authentication key from a beam.chat.join call.
 * @returns {Promise.<>}
 */
function createChatSocket (userId, channelId, endpoints, authkey) {
    // Chat connection
    const socket = new BeamSocket(endpoints).boot();

    // Greet a joined user
    socket.on('UserJoin', data => {
        socket.call('msg', [`Hi ${data.username}! I'm pingbot! Write !ping and I will pong back!`]);
    });

    // React to our !pong command
    socket.on('ChatMessage', data => {
        if (data.message.message[0].data.toLowerCase().startsWith('!ping')) {
            socket.call('msg', [`@${data.user_name} PONG!`]);
            console.log(`Ponged ${data.user_name}`);
        }
    });

    // Handle errors
    socket.on('error', error => {
        console.error('Socket error', error);
    });

    return socket.auth(channelId, userId, authkey)
    .then(() => {
        console.log('Login successful');
        return socket.call('msg', ['Hi! I\'m pingbot! Write !ping and I will pong back!']);
    });
}

Prerequisites

  1. Java 1.8 or above
  2. A Java IDE such as:
    1. Eclipse
    2. IntelliJ
    3. NetBeans
  3. A Java Project Manager such as:
    1. Maven
    2. Gradle

Project Setup

Setup a standard project for your environment and include beam-client-java as a dependancy.

To setup beam-client-java, first add the Mixer repo to your pom.xml as a repository as follows:

<repositories>
  <repository>
    <id>beam-snapshots</id>
    <url>https://maven.mixer.com/content/repositories/snapshots/</url>
  </repository>
</repositories>

And secondly, add this project as a dependency in your pom.xml:

<dependencies>
  <dependency>
    <groupId>pro.beam</groupId>
    <artifactId>api</artifactId>
    <version>3.0.3-SNAPSHOT</version>
  </dependency>
</dependencies>

To setup beam-client-java, first add the Mixer repo to your build.gradle as a repository as follows:

repositories {
    maven {
        name = "beam"
        url = "https://maven.mixer.com/content/repositories/snapshots"
    }
}

And secondly, add this project as a dependency in your build.gradle:

dependencies {
    compile "pro.beam:api:3.0.3-SNAPSHOT"
}

Writing the Code

Let's start by creating a Main class for our tutorial and importing required libraries. We'll also instantiate a MixerAPI object with an implicit OAuth token. The required scopes are chat:connect chat:chat.

import pro.beam.api.BeamAPI;
import pro.beam.api.resource.BeamUser;
import pro.beam.api.resource.chat.BeamChat;
import pro.beam.api.resource.chat.events.IncomingMessageEvent;
import pro.beam.api.resource.chat.events.UserJoinEvent;
import pro.beam.api.resource.chat.methods.AuthenticateMessage;
import pro.beam.api.resource.chat.methods.ChatSendMethod;
import pro.beam.api.resource.chat.replies.AuthenticationReply;
import pro.beam.api.resource.chat.replies.ReplyHandler;
import pro.beam.api.resource.chat.ws.BeamChatConnectable;
import pro.beam.api.services.impl.ChatService;
import pro.beam.api.services.impl.UsersService;

import java.util.concurrent.ExecutionException;

public class Chat {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        BeamAPI beam = new BeamAPI("Click here to get your Token!");
    }
}

Next we need to get a reference to a MixerChat object that we can connect to. To do this we'll login, using the details from above and use the MixerUser object to get their MixerChannel. We can then pass this to the ChatService to get a MixerChatConnectable object. This object allows us to connect to chat!

//...
BeamUser user = beam.use(UsersService.class).getCurrent().get();
BeamChat chat = beam.use(ChatService.class).findOne(user.channel.id).get();
BeamChatConnectable chatConnectable = chat.connectable(beam);
//...

Now we need to connect and authenticate with an AuthenticateMessage. As this is sent asyncronously we'll need to wait for it to finish before we can send anything to chat. Our ReplyHandler takes care of that.

if (chatConnectable.connect()) {
    chatConnectable.send(AuthenticateMessage.from(user.channel, user, chat.authkey), new ReplyHandler<AuthenticationReply>() {
        public void onSuccess(AuthenticationReply reply) {
            chatConnectable.send(ChatSendMethod.of("Hello World!"));
        }
        public void onFailure(Throwable var1) {
            var1.printStackTrace();
        }
    });
}

With authencation and connection out of the way we just need to hookup the greet event and the !ping command. The greet event is setup by registering an EventHandler for the UserJoinEvent.

//...
chatConnectable.on(UserJoinEvent.class, event -> {
    chatConnectable.send(ChatSendMethod.of(
        String.format("Hi %s! I'm pingbot! Write !ping and I will pong back!",
        event.data.username)));
});
//...

The last thing to do is to setup the !ping command. This is sent on a IncomingMessageEvent and can be setup in much the same way.

//...
chatConnectable.on(IncomingMessageEvent.class, event -> {
    if (event.data.message.message.get(0).text.startsWith("!ping")) {
        chatConnectable.send(ChatSendMethod.of(String.format("@%s PONG!",event.data.userName)));
    }
});
//...

Putting everything together into one file you get a completed ping-pong Java Mixer Bot!

import pro.beam.api.BeamAPI;
import pro.beam.api.resource.BeamUser;
import pro.beam.api.resource.chat.BeamChat;
import pro.beam.api.resource.chat.events.IncomingMessageEvent;
import pro.beam.api.resource.chat.events.UserJoinEvent;
import pro.beam.api.resource.chat.methods.AuthenticateMessage;
import pro.beam.api.resource.chat.methods.ChatSendMethod;
import pro.beam.api.resource.chat.replies.AuthenticationReply;
import pro.beam.api.resource.chat.replies.ReplyHandler;
import pro.beam.api.resource.chat.ws.BeamChatConnectable;
import pro.beam.api.services.impl.ChatService;
import pro.beam.api.services.impl.UsersService;

import java.util.concurrent.ExecutionException;

public class Chat {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        BeamAPI beam = new BeamAPI("Click here to get your Token!");

        BeamUser user = beam.use(UsersService.class).getCurrent().get();
        BeamChat chat = beam.use(ChatService.class).findOne(user.channel.id).get();
        BeamChatConnectable chatConnectable = chat.connectable(beam);

        if (chatConnectable.connect()) {
            chatConnectable.send(AuthenticateMessage.from(user.channel, user, chat.authkey), new ReplyHandler<AuthenticationReply>() {
                public void onSuccess(AuthenticationReply reply) {
                    chatConnectable.send(ChatSendMethod.of("Hello World!"));
                }
                public void onFailure(Throwable var1) {
                    var1.printStackTrace();
                }
            });
        }

        chatConnectable.on(IncomingMessageEvent.class, event -> {
            if (event.data.message.message.get(0).text.startsWith("!ping")) {
                chatConnectable.send(ChatSendMethod.of(String.format("@%s PONG!",event.data.userName)));
            }
        });

        chatConnectable.on(UserJoinEvent.class, event -> {
            chatConnectable.send(ChatSendMethod.of(
                    String.format("Hi %s! I'm pingbot! Write !ping and I will pong back!",
                    event.data.username)));
        });
    }
}

Raw API

The code here is not meant for a real world implementation but rather as an explanation/help for custom implementors.

Prerequisites

  1. A bash shell
  2. curl
  3. wscat
  4. jq

Usage

First, we'll show how to perform a simple request with curl. We simply send our OAuth token and store the resulting json in a variable for later use.

#!/bin/bash

# authenticate with backend
# hush progress
# we send json as content type
# a json body as payload
# and store the cookies in a jar!
userInfo=$(curl -X POST \
-s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer Click here to get your Token!"
https://mixer.com/api/v1/users/current )

# jq . <<< $userInfo # use for inspection of data

channelId=$(jq .channel.id <<< $userInfo); # extract channel id
userId=$(jq .id <<< $userInfo); # extract user id
echo $channelId $userId;

In the next step we use the previously gathered info to request the chat server auth info.

# get server connection info
# use cookie we got
chatJoinInfo=$(curl \
-s \
-H "Content-Type: application/json" \
-H "Authorization: Bearer Click here to get your Token!"
https://mixer.com/api/v1/chats/$channelId )

wsServer=$(jq -r .endpoints[0] <<< $chatJoinInfo)
authKey=$(jq .authkey <<< $chatJoinInfo)

echo $wsServer $authKey;

Now that we have all required information, we can establish a connection to the chatserver with wscat. As shown in the next snippet, we send a authentication payload first. Please note that all previous steps are optional if you just want to listen to the chat.

$ wscat --connect "$wsServer"
connected (press CTRL+C to quit)
> {"type": "method","method": "auth","arguments":[CHANNELID, USERID,"AUTHKEY"],"id":0}
< {"type":"event","event":"UserJoin","data":{"username":"USERNAME","roles":[,"User"],"id":345}}
< {"type":"reply","error":null,"id":0,"data":{"authenticated":true,"roles":["User"]}}

Typically the first messages we receive are our own UserJoin event and an acknowledgement of our auth.

Want More Info?

If you'd like more information on our Chat system, check out the reference guide.

Need more help?

If you're still not sure, or would like some help, hit us up on Gitter!