Unsplash chatbot for Discord, Pt. 2: more ways to bring pictures to Discord
Tags: chatbot, discord, javascript, api
Published: 2022-02-04 20:11:25

In the previous post we've built a very basic Discord bot that can search Unsplash images for user specified query and output results to back a channel. In this article we'll expand on that functionality and allow Discord users to schedule a random picture for a later time.

## Just AI Conversational Platform (JAICP)
The JAICP part of our bot needs two changes: implement `/random` endpoint of the Unsplash API and add two new states to the bot itself - one to process user's request to schedule a picture for later and  another to fulfill it.

### Unsplash API client
JS client is located in the `script/functions.js` file and here's the code that implements the `/random` endpoint:
```javascript
var UnsplashAPI = {
    // ...
    
    random: function () {
        var picture = $http.get("https://api.unsplash.com/photos/random", {
            dataType: "json",
            headers: {
                "Authorization": "Client-ID //replace with your access key"
            }
        });
        return picture.data;    
    }
};
```
This is simpler than search because it doesn't require any request parameters and returns only one image object.

### Scheduling intent
Now we need to define an intent to schedule a picture for later and a slot with time for when to post. Go to `CAILA -> Intents` and create a new intent, I called it `RandomPicForLater`. Unlike our previous intent, `Search`, this one will have a slot. 

Slots are similar to query parameters in HTTP GET request and slot filling is a task that conversational systems perform to gather slot values from a user.

Our `RandomPicForLater` intent will have one slot called `reminderTime` and will be of type `@duckling.time`. [Duckling](https://github.com/facebook/duckling) is a library that extracts entities from text, and it is one of the tools used in JAICP for this purpose. Entity types in Duckling are called dimensions and there's a number of them built in, among them is `Time` which suits us perfectly since we need to ask users when they want us to schedule a post for and then parse a text input into a `datetime` object. 

User's intent might be expressed as "schedule unsplash pic" or "schedule a random picture". In return, we might ask something like "When do you want me to schedule it?" to get the posting time. Fill these values in the corresponding fields ⬇️

![Editing Intent in Just AI Conversational Platform (JAICP)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nppuat65m2fz2rzz9n91.png)

### Fulfillment
Back to the editor, add the following code to `main.sc`:
```javascript
    ...
    state: ScheduleRandom
        intent!: /RandomPicForLater
        script:
            $session.reminderTime = $parseTree["_reminderTime"];
            var event = $pushgate.createEvent(
                $session.reminderTime.value,
                "scheduleEvent"
            );
            $session.reminderId = event.id;
            $temp.reminderTime = moment($session.reminderTime.value).locale("en").calendar();
        a: Very well, your random Unsplash picture will arrive at {{$temp.reminderTime}}.
```
This a new state `ScheduleRandom` which is triggered by `RandomPicForLater` intent. 

What happens in the `script` block is interesting, because we first retrieve that `reminderTime` slot value and then make use of JAICP's [Pushgate API](https://help.just-ai.com/docs/en/JS_API/built_in_services/pushgate/pushgate) that allows you to create outbound communications, as in define custom events, handle them, send outbound messages and even have bots notify your systems via webhooks. Here specifically we schedule a new `scheduleEvent` at user requested time and then handle it in the next state ⬇️

```javascript
    state: Remind
        event!: scheduleEvent
        script:
            var picture = UnsplashAPI.random();
        
            $response.replies = $response.replies || [];
            var content = [];
            log("picture_desc= " + picture.urls.description);
            log("picture_url= " + picture.urls.small);
            content.push({
                "title": picture.description || "No description",
                "image": picture.urls.small,
                "url": picture.links.html,
                "btnText": "View on Unsplash"
            });
                    
            var reply = {
                "type": "carousel",
                "text": "Your scheduled random picture",
                "content": content
            };
            $response.replies.push(reply);
```
Notice that `Remind` state is triggered not by an intent or a pattern match, but by `scheduleEvent`. The handler then does two things:

- get a random picture from Unsplash API client
- build a reply of type `carousel`, similar to what we did in [Part 1](https://dev.to/hiisi13/building-an-unsplash-bot-for-discord-60i), but with just a single item

The chatbot is now fully functional which you can verify by trying it in the test widget:
![Testing Unsplash chatbot in JAICP test widget](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ukwc253y6l444nfteqqn.png)

### Extending Discord adapter
The only problem now is that Discord adapter to Chat API only works in request-response fashion and doesn't actively listen for messages incoming for the chatbot server. Let's fix that.

[JAICP's Chat API](https://help.just-ai.com/docs/ru/api/chat_api/) provides an `events` endpoint which clients can use to fetch server-initiated events. Every time a new Discord user starts a conversation with the bot, we'll start a very minimalistic loop that will periodically try to fetch server events that occurred after the last known response ⬇️
```javascript
const startPollingLoopForUser = async function (userId, channel) {
  setInterval(async () => {
    const endpoint = `https://app.jaicp.com/chatapi/${process.env.JAICP_CHAT_API_KEY}/events`;
    const eventsResponse = await axios.get(endpoint, {
      params: {
        clientId: userId,
        ts: lastJaicpMessageTimestamps[userId],
      },
    });
    eventsResponse.data.events.forEach((event) => {
      if (event.type === "botResponse") {
        const ts = Date.parse(event.event.timestamp);
        if (
          event.event.questionId !== lastQuestionIds[userId] &&
          ts > lastJaicpMessageTimestamps[userId]
        ) {
          lastJaicpMessageTimestamps[userId] = ts;
          lastQuestionIds[userId] = event.event.questionId;
          event.event.data.replies.forEach((reply) => {
            processReply(channel, reply);
          });
        }
      }
    });
  }, POLLING_INTERVAL_MS);
};
```
Here, we check for events of type `botResponse` and then perform some basic deduplication to ensure messages aren't sent to Discord more than once.

Going back to the main request-response handler, we now need to update event timestamp and question history and start a polling from above for a given user.

```javascript
lastJaicpMessageTimestamps[message.author.id] = Date.parse(
        response.data.timestamp
      );
lastQuestionIds[message.author.id] = response.data.questionId;
if (!pollingLoops.hasOwnProperty(message.author.id)) {
    pollingLoops[message.author.id] = true;
    startPollingLoopForUser(message.author.id, message.channel);
}
```
Please note that for the purpose of this article I'm using the very basic data structures and don't persist data between adapter restarts, so this code is by no means production ready, but still give you a decent foundation to build a fully fledged adapter for virtually any chat platform.

When you run a complete example and test it in Discord, it should look something like this ⬇️

![Scheduling a random Unsplash picture in Discord via JAICP chatbot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ui1mot8nwt7d8t8rlzwc.png)

## Conclusion
Expanding on Part 1, our Discord chat bot can now can send a random picture at requested time. Moreover, Discord-to-JAICP adapter now can handle both traditional request-response interchange and server initiated events.

As usual, complete source code is available on Github - [adapter](https://github.com/hiisi13/jaicp-discord-adapter) and [chatbot](https://github.com/hiisi13/jaicp-discord-unsplash-bot) (make sure to check out `part-2` branch for both).

Cover photo by [Volodymyr Hryshchenko](https://unsplash.com/@lunarts) on [Unsplash](https://unsplash.com/photos/V5vqWC9gyEU).