mike-neckのブログ

JavaかJavaFXかJavaEE(なんかJava8が多め)

daabの開発をKotlinでできるようにする

今いる会社の提供するdirectというチャットサービスではボットの開発ができて、そのsdkdaab(direct agent assist bot)というらしい。

で、daabチームが専修大学ハッカソンを共催するということで、僕も自分の会社のボットくらい作れないと恥ずかしいなと思ったので1日だけ参加してきました。


daabはhubotをdirect用に改造した感じのもので、次のようなプログラムで簡単にボットが記述できます。

module.exports = robot => {
    robot.hear(/^today/i, res => {
        var now = new Date();
        res.send(dateFormat(now, "yyyy/mm/dd"));
    });
};

ところが、困ったことにどういう関数があるのか、何を引数に取るのか、ドキュメントを漁ってもいまいちわかりません。

ドキュメントを漁ってるうちに、Typescriptでhubotを書きたい人なら型定義ファイルを作っているに違いないと思い立って、探してみたら下の記事を見つけました。

qiita.com


この記事にかかれていた型定義をそのままコピってTypescriptでdaabを書いてもいいんだけど、もうひとひねり入れて、ts2ktにかけてKotlinの型情報に変換してdaabをKotlinで書けるようにしてみました。

ts2ktをしてできた型情報(一部省略/修正)

@file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS")
@file:JsQualifier("hubot")
package hubot

external interface Robot {

    fun hear(regex: RegExp, callback: ((res: Response) -> Unit)? = definedExternally /* null */)
    fun hear(regex: RegExp, options: Function<*>, callback: ((res: Response) -> Unit)? = definedExternally /* null */)
    fun hear(regex: RegExp, options: Json, callback: ((res: Response) -> Unit)? = definedExternally /* null */)

}

external interface Response {

    fun send(vararg strings: String)

}

ボット部分のコードは見事に綺麗なKotlinで書けました。

package com.example

import hubot.Response
import hubot.Robot
import kotlin.js.RegExp

val app: (Robot) -> Unit = { robot: Robot ->
    robot.hear(RegExp("now")) { res: Response ->
        res.send(dateTime())
    }
}

kotlin-jsはkotlinc-jsではなく、gradleでコンパイルしました。なお、 build.gradle はこんな感じ

buildscript {
    ext.kotlin_version = '1.1.2-4'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin2js'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
}
compileKotlin2Js {
    kotlinOptions.outputFile = "${projectDir}/daab/scripts/lib/daab.js"
    kotlinOptions.moduleKind = "commonjs"
    kotlinOptions.sourceMap = true
}
task generateAppJs {
    def jsFile = file("$projectDir/daab/scripts/application.js")
    outputs.file jsFile
    doLast {
        def content = """let kotlin = require("kotlin");
let daab = require("./lib/daab");
module.exports = daab.com.example.app;
"""
        jsFile.write(content, 'UTF-8')
    }
}
tasks.compileKotlin2Js.dependsOn tasks.foreverIgnore
tasks.compileKotlin2Js.finalizedBy tasks.generateAppJs

実行した結果は次のとおり、見事に動かせました

f:id:mike_neck:20170611113817p:plain


なお、気をつけるところがいくつかあって、KotlinのAPIJVM環境のプログラムを書く場合のAPIと若干異なっているところです。例えば Date 型はありますが、 java.util.Date ではなくて、 kotlin.js.Date になっていて getTime() メソッドも Long ではなく、 Double を返すなど異なっています。

今回の成果をもう少し汎用的にして、かつ、同僚のエンジニアにAPIを確認して型情報を明確にした上でgradleプラグインを出そうかなと思っています。