DEV Community

Cover image for How to use NPM modules in Kotlin/JS - Discord Bot Series (Part 1)
Kevin Schildhorn
Kevin Schildhorn

Posted on

How to use NPM modules in Kotlin/JS - Discord Bot Series (Part 1)

KotlinJS is an exciting new method of creating javascript projects that combines the benefits of javascripts speed and versatility and kotlins strong typing and conciseness. Plus the ability to easily add testing and even share code across platforms.

Since KotlinJS transpiles kotlin code to javascript it gives you all the advantages of executing a NodeJS program, while enjoying the benefits of the kotlin language.

There are many use cases for KotlinJS and in this series we'll be creating a Discord bot. Discord is a great platform for keeping in touch with friends, and adding a bot can enrich that experience. We can create this bot by using the NPM module for DiscordJS.

So how do you create a discord bot using KotlinJS? Well since this is a somewhat large topic this post will be the first in a series of three posts:

  1. In this post we'll go over how kotlinJS uses node modules. This is more of an intro to KotlinJS and doesn't cover Discord.
  2. In the next post we'll go over implementing DiscordJS and responding to messages.
  3. In the last post we'll add some unit tests to make sure the bot works as expected before deploying.

Overview

By the end of this series we'll have a small discord bot that responds to a specific message with the computer name it's running on. In this first part the bot will just print to console on startup, but responding to messages will come later in another part.

Prerequisites

  • Some Kotlin Experience
  • Some JS Experience
  • IntelliJ IDEA (I'm using Community Edition)

New Project

To get started, first follow the js project setup on the kotlin site. (Be sure to choose NodeJS Application, not browser)

This will create a project with a Main.kt file and a greeting function. To run the project, call ./gradlew run in the terminal. You should see Hello, YOUR_BOT_NAME in the terminal!

Importing Node Modules in KotlinJS

Lets start off by getting familiar with how KotlinJS handles Node Modules.

To have the bot send a message with its name, let's use a simple node module called computer-name. As mentioned in the project setup, node modules are implemented as dependencies in the build.gradle file. No need for require or special imports. Add this line to your dependencies.

dependencies {
    implementation(npm("computer-name", "0.1.0"))
}
Enter fullscreen mode Exit fullscreen mode

If you try to call computerName() now, you'll get an Unresolved reference error. This is because of a crucial difference between javascript and kotlin:

Javascript is loosely typed and kotlin is strongly typed

So in order to use this function we need to define it by using external functions.

External

The kotlin docs mention the External Modifier, which is used to declare pure javascript code. This tells the compiler that we're expecting this class or function to be defined externally by the node module. Whenever we want to use a module we have to define functions and classes. An important note about this is:

You only need to define what you are using

So you don't need to define the entire module, just what you need to reference.

So for us to use computerName() we need to define it and tell the compiler where it's defined, like so:

@JsModule("computer-name")
external fun computerName(): String
Enter fullscreen mode Exit fullscreen mode

Note that we also need to annotate the function with the module name or else we'll hit a runtime error(compilerReferenceError).

This definition was easy to figure out based on the npm page, but as we'll see you may have to dig into the documentation and source code of node modules to find the original definition.

Module System

Another thing we have to define is the module system. You can find more information here, but in short KotlinJS supports UMD, AMD and commonJS systems. commonJS is widely used for NodeJS so we'll add useCommonJs() to our build.gradle file.

kotlin {
    js(IR) {
        useCommonJs()
    }
}
Enter fullscreen mode Exit fullscreen mode

Now try adding println(computerName()) to main() and run the project.

You should see the name of your computer in the terminal! congrats! Now let's look at importing classes.

Importing NodeJS Classes

So far we've covered importing a function by using computerName() as an example, but that's a small module with one function. It's also important to be able to import classes and interfaces from modules to use in our project.

For this example we'll use youtube-search, which is a small module that lets you search for youtube videos (Note: You won't get results without a youtube api key, but the calls will still work which is all we need). From the npm page you can see there's a search function that returns custom results. So how do we define this in kotlin?

While there is definition in this module, we'll need to go to the repository to get more information. Lucky for us this module is conveniently written in typescript so it's very easy to find the definition, which is written in index.d.ts.

Dukat

Before we get into manually converting typescript to kotlin code, I should mention there is a tool to do this automatically called Dukat. It is a powerful tool that can help with easy conversion, however for this post I want to go over how to manually convert typescript to kotlin so that you have a good understanding of how it works.

Manually Converting Typescript

From the repo open the index.d.ts file and scroll to the bottom to see the search function.

declare function search(
  term: string,
  opts: search.YouTubeSearchOptions,
  cb?: (err: Error, result?: search.YouTubeSearchResults[], pageInfo?: search.YouTubeSearchPageResults) => void
): Promise<{results: search.YouTubeSearchResults[], pageInfo: search.YouTubeSearchPageResults}>;
Enter fullscreen mode Exit fullscreen mode

We can see it takes in:

  • a string
  • a custom options class
  • a callback that returns an optional error, results array, and pageInfo array

The fuction then returns a Promise. Luckily kotlin already defines errors in the stdlib and it also includes an import for promises:

import kotlin.js.Promise
Enter fullscreen mode Exit fullscreen mode

So the difficult parts are the Results and options. We can see that YouTubeSearchResults and YouTubeSearchPageResults are interfaces, so we can easily define them like so:

@file:JsModule("youtube-search")
// Note that for multiple definitions in the same file you can use @file

external interface YouTubeSearchPageResults {
  val totalResults: Int
  ...
}

external interface YouTubeSearchResults {
  val id: String
  ...
}
Enter fullscreen mode Exit fullscreen mode

Now for YouTubeSearchOptions we could do the same approach, but there's another option we can use.

Json

We can simply use a Json class object, which acts similarly to a HashMap. This way we can just pass in what we want without defining the entire interface. This also works well if you can't find a clear definition of an object being passed in.

import kotlin.js.Json

val options = json(
    Pair("maxResults", 1),
    Pair("key", YOUR_YOUTUBE_API_KEY) // Leave this blank if you don't have one
)
Enter fullscreen mode Exit fullscreen mode

So from all of this, we can then create the external search function:

@JsModule("youtube-search")
external fun search(
    term: String,
    opts: Json,
    cb: (
        err: Error?,
        result: Array<YouTubeSearchResults>?,
        pageInfo: YouTubeSearchPageResults?
    ) -> Unit // Unit acts as Void in kotlin
) : Promise<Json>

search("your search", options) { err, result, pageInfo ->
    print("Youtube callback $err, $result, $pageInfo\n")
}
Enter fullscreen mode Exit fullscreen mode

Try adding this to your project and run. If you don't have an api key you should see this in the terminal:

Youtube callback Error: Request failed with status code 403, null, null
Enter fullscreen mode Exit fullscreen mode

If you do have the api key you should see this:

Youtube callback null, [...], [object Object]
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations you have a working KotlinJS project with imported NPM modules! In the next post we'll go over the specifics of creating our Discord bot using DiscordJS.

Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at @kevinschildhorn on Twitter.

Top comments (0)