Building an Unsplash chatbot for Discord
Dmitry Kozhedubov
Posted on January 27, 2022
Discord is a popular realtime chat application that has gamer-centric roots but has recently repositioned itself towards online communities in general. Unsplash has long become a de-facto source of gorgeous free to use pictures, powered by its amazing community of creators. It is only natural to marry the two and in this two posts series I'll show you how to build a chatbot that brings pictures from Unsplash to Discord chats in a couple of different ways.
Tools
In order to build the chatbot I'll obviously utilize Unsplash API which is really simple and easy to use.
For the chatbot logic I will use Just AI Conversational Platform (JAICP), which is an enterprise-grade platform that enables customers to design, develop, deploy and operate intelligent conversational AI assistants in a wide variety of text and voice channels (disclaimer: I work there). One benefit of using such platform is if you build a Discord bot and then decide to use it in Facebook Messenger, you can do that in a few clicks without changes to your code. We provide a pretty generous free tier, which means you could run a bot for your community for free or at a low fee.
Finally, because JAICP doesn't have a native Discord integration (yet) but has an API that allows for 3rd party integrations, I will build a Node.js adapter that translate messages from Discord to JAICP and vice versa.
Discord bot
The first step is to step a Discord application for your bot through the Developer Portal.
On the Applications page click New Application button in the top navigation bar. In the popup window enter something like "jaicp-discord-unsplash" and then click Create. You will be redirected to your application's page where you need to select Bot tab on the left hand side. Click Add Bot and then Yes, do it! - this will turn your app into a bot and provide you with settings, specifically bot token that we will use later on.
Remember to keep your bot token private and immediately regenerate it if you suspect it's been compromised as it gives the full access to your bot actions.
Just AI Conversational Platform (JAICP)
Next, we need to create a JAICP account. After you signed up using a method of your choosing, click Create Project button on the left hand side, choose Create from scratch option in the wizard, and finally specify a name, like "jaicp-discord-unsplash". For the purpose of this article I'll keep my bot's code in the built-in repository but alternatively you can choose to use an external Git provider like Github.
Looking around
You will not actually start from scratch but a Hello World example that will show a bit of a powerful JAICP DSL which is based on YAML and Javascript and provides many useful abstractions that allow you to build complex chatbots and voice assistants across different channels very quickly and efficiently.
The main scenario file is main.sc
where we'll basically define bot's state machine (finite-state machine, FST), transitions between states in response to user's input and responses that the bot provides back to users.
Now let's create a bot that will activate on phrases like search Unsplash or find images on Unsplash, ask a user what they'd like to search for, perform an API request and finally return results.
Unsplash part
But before we develop any scenario code, let's write a simple Javascript client for Unsplash API.
var UnsplashAPI = {
search: function (query) {
var pictures = $http.get("https://api.unsplash.com/search/photos?page=1&per_page=3&query=" + query, {
dataType: "json",
query: {
query: query
},
headers: {
"Authorization": "Client-ID // replace with your Unsplash API key"
},
timeout: 10000
});
return pictures.data;
}
}
This code defines a very simple function that performs a call to Unsplash API and returns the first 3 pictures that match a query in a JSON format. It uses JAICP's built-in $http
service that allows you to integrate bots with external systems.
Intent recognition
Next, let's setup intent recognition for the bot, even if there's only one intent for now. Go to the Intents page under CAILA (Conversational AI Linguist Assistant, which is JAICP's NLU component) folder on the left hand side and click Create Intent at the top.
Name the intent search
and under training phrases enter some that you would normally associate with searching for images on the internet - some examples include search unsplash, find images. Then you can test the model right there and verify that search
intent is recognized correctly.
Scenario
Now that API and intents are sorted out, let's turn our attention to the bot scenario. Go back to main.sc
and append the following code into it:
state: SearchUnsplash
intent: /search
go!: /SearchUnsplash/RequestQuery
state: RequestQuery
a: What should I search for?
state: SearchPictures
q: *
script:
var query = $request.query;
var pictures = UnsplashAPI.search(query);
$response.replies = $response.replies || [];
var content = [];
pictures.results.forEach(function (picture) {
content.push({
"title": picture.description,
"image": picture.urls.small,
"url": picture.links.html,
"btnText": "View on Unsplash"
});
});
var reply = {
"type": "carousel",
"text": "Unsplash search results for \"" + query + "\":",
"content": content
};
$response.replies.push(reply);
go: /
There are a few things going on here. First, SearchUnsplash state of the state machine is defined, which is soft of an activation state for our bot. The bot will enter into it every time search
intent is recognized from user's input. All it does is to redirect to the next (nested) state, /SearchUnsplash/RequestQuery, which will ask a user what they want for search for. Nesting here means, that once the root intent is recognized, subsequent user input will be processed (intent recognition, slot filling) in the context of that root intent.
Once a user enters a search query it will put the chatbot into SearchPictures state because it has a wildcard (*) matching - indeed, search query is an arbitrary sentence.
Finally we have our search query and can fulfill user's intent by putting a few lines of Javascript in the script
block. Here we call an Unsplash API wrapper that we created earlier and transform results into a reply message of type Carousel, which is tailor-made for outputting lists of data, even though the end look-and-feel may vary based on a channel, whether it's Discord or Facebook or something else.
Setting up Chat API
The last thing we need to do in JAICP is to set up Chat API credentials for us to be able to communicate with external chat platform such as a Discord.
Go to Channels, click Connect channel under the Inbound section and select Chat API. Once you hit Save, you will be able to grab access token which is requires to the final step.
Putting it all together
One key component is still missing - as I just mentioned, JAICP currently doesn't a native Discord integration, but does have an extension point called Chat API, which allows developers to integrate conversational AI solution into a pretty much any chat platform.
I've created a really simple adapter in Node.js that listens for messages on Discord, interacts with JAICP via Chat API and then replies back to Discord. It makes use of an excellent Discord.js library and also axios for http requests.
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES],
});
client.on("message", function (message) {
if (message.author.bot) return;
message.channel.sendTyping();
axios
.post(
`https://app.jaicp.com/chatapi/${process.env.JAICP_CHAT_API_KEY}`,
{
query: message.content,
clientId: message.author.id,
},
{
headers: {
"Content-Type": "application/json",
},
}
)
.then(function (response) {
response.data.data.replies.forEach(async (reply) => {
if (reply.type === "text") {
message.channel.send(reply.text);
} else if (reply.type === "carousel") {
message.channel.send(reply.text);
reply.content.forEach((item) => {
let embed = new MessageEmbed()
.setImage(item.image)
.setDescription(item.title || "No description")
.setURL(item.url);
let actionRow = new MessageActionRow();
let b = new MessageButton()
.setLabel(item.btnText)
.setStyle(5)
.setURL(item.url);
actionRow.addComponents(b);
message.channel.send({ embeds: [embed], components: [actionRow] });
});
}
});
})
.catch(function (error) {
console.log(error);
});
});
client.login(process.env.BOT_TOKEN);
We instantiate a websocket client that listens for Discord messages to come in, passes the text content to JAICP and transforms the replies back into Discord format. In particular, it uses embeds for images themselves and action rows/buttons to provide a link back to Unsplash - it's a nice thing to do even if Unsplash doesn't require attribution. Although JAICP can return many more than just two reply types, text
and carousel
are the only two we need to handle for this tutorial.
For the purpose of this article I just run it locally but you can obviously deploy it to something like Heroku.
Once you run it and try the bot in Discord it should look something like this:
You can see that I asked the bot to search for pictures of coffeeshops, and indeed I got what I wanted.
Conclusion
In this tutorial we saw how to add a conversational bot to Discord that can potentially do much more than just post pictures, even though pictures from Unsplash are typically gorgeous. This is actually a part 1 of the two piece series - in part 2 I'll show you how to set up a scheduled photo of the day post.
You can find the code both of the chatbot project and Discord adapter on Github.
Naturally, the cover photo of this post is from Unsplash Photo by Chuck Fortner.
Posted on January 27, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
February 4, 2022