Introduction

I recently signed up to the Bkmark app, a tool created to store and organise the endless amount of bookmarks we all collect whilst surfing the internet, and have started using it to store everything from important business links to interesting articles I'll definitely read later.

Whilst Bkmark does have a Twitter reply bot to save tweets to my collections, I realised I spend a lot of time on my phone finding articles and websites I'd like to visit later but being unable to quickly add it to my collections. Additionally, I use Discord a lot on a daily basis, so I thought "Why not make a Discord bot using Bkmark's API?".

Turns out, there's no API.

I asked Boris, the creator, and it's somewhere on the roadmap several months down the line, which is perfectly normal. Boris is doing great work at the moment being a solo-founder of Bkmark and an API is probably the last thing on his mind.

The Unofficial API


So I took things into my own hands, I realised that behind the web app there must be a set of endpoints used for collecting and posting data to Bkmark's system. and with this decided to open the Network tab the next time I was on the app.

After a few refreshes and clicking about the place, I had a short list of endpoints to test out. I used Postman, and clumsily sent a request to an endpoint.

"Unauthorised Request" - Ah yes, I need my login token.

Login token acquired, I sent another request and miraculously got an authenticated request back.

{
    "message": "User found.",
    "user": {
    ...
    },
    "request": {
    ...
    },
    "status": 200
}

I'm glad a user was found, for a moment I thought I didn't exist.

Success! The first endpoint worked, I played around with the other endpoints I gathered earlier in this adventure and noted down how the API interacts with Collections and Organisations.

Lets Build A Bot!

The actual bot framework is based on a modified version of Walter, a bot that we run at Fullstack.chat. It handles parsing commands and running functions based on commands it receives.

Before I added commands, I setup an API service file that would handle getting and posting requests to Bkmark and handling authentication.

const getUser = async () => {
    try {
        let response = await bkmark({
            method: 'get',
            url: '...',
            headers: {
                authorization: `Bearer ${token}`
            }
        })
        return response.data
    } catch(e) {
        console.log(e)
        return e
    }
};

The bkmark() function above is just an Axios.create() wrapper with a base URL already setup. This time I remembered to send a token along, which I originally stored in a .env file.

With the service file done and the endpoints setup to start playing with, it was time to start writing commands. First up, a User command that would return the currently authenticated user; I wanted something simple just to test everything worked.

Hello there.

Perfect, I worked my way through the other three commands and finished MK1 of the bot, it was just enough to work for me and I liked it. But then I had a new idea.

Open-sourcing the Bot.

"What if others would like to use this bot?" I thought to myself. "That's a good idea".

It's a good idea because it has some harder challenges to overcome to get the bot to work for anyone.

These were a few problems it had:

  • Tokens were set in a .env file and needed to be interacted with by the bot to make it more user friendly.
  • UUIDs were used to interact with Collections, these were alphanumerical strings that no one would ever remember.
  • A predefined Collection and Organisation UUID was already set up in requests which would need to be dynamically changed depending on the authenticated user.

The one solution to all of this was adding a local JSON database to replace predefined variables such as the JWT Token with dynamic data. I installed simple-json-db and wrote a small Database service file that I could import anywhere in the bot and interact with the local JSON file.

The Setup Function

Before the bot could start responding to commands, it needed to gather some information about the user. This came as a setup function that was run if the property "setup" was missing from the JSON file.

This setup file did several things that made Collections and Organisations easier to interact with. Firstly, it gathered the User's organisations and set the default organisation in the database as the first organisation in the array.

Secondly, it gathered each collection in the Organisation and saved each to a Collections property to the database with their UUID and corresponding name. This was important as it meant I could now look up collection UUIDs sent from the API and match them to their name, making responses from the Bot more user friendly.

Thirdly, it sets a default collection UUID in the database which is used in case the User forgets or doesn't specify a collection when sending the Add command. This default collection can be changed using the Default command and uses the collections in the database to match the collection Name to it's UUID.

The setup function in action.

Overall, this Setup function solved two of the three problems I listed above. Collections were no longer predefined variables and could be changed using Bot commands and I could now modify requests to Bkmark to send data from the database.

Solving these two problems were the hardest part of building the bot as I had to write functions to match a Collection's name to it's UUID and vice versa which lead to me sending several dozen requests to Bkmark in the process.

How many Google bookmarks do I need?

The last problem was the easiest, I moved the JWT token out of the .env file and into the database.

When the Bot starts, it checks if the JWT token exists in the database and if not tells the Users. The user can then save their token to the database using the Token command. Additionally, I updated each request to use the Token from the database instead of the .env file.

Cleaning up

The final part of making this bot was cleaning up code and making the messages sent from the Bot look a little nicer. I headed over to Discohook and created some Embeds that the bot would send instead of messages.

And with that done, I gave the Bot a name, made a README file and pushed the code to GitHub.

Welcome to the world, Bkmark Boi.

Feel free to clone the repo and run your own instance of the Bot. (Some setup is required)

Conclusion

In conclusion, this was a very fun two day project to undertake and Boris' passion for his creation can be seen, not only on the web app, but in the API too, which was very well-composed and easy-to-reverse-engineer (I'm sure he never expected anyone to see it except himself).

Happy coding, friends. 😉

Boris Tane:
Twitter

Bkmark:
Twitter
Website

Me:
Twitter
Website (You are here)

P.S Boris, if you ever need someone to test/document your APIs, 👋.