Node.jsでByteDance製ビジネスアプリLarkのBotを作る

#Larkってなに
Larkとは、シンガポールに本社を置くLark Technologiesが開発するアプリでチャット、ビデオ、カレンダー、スプレッドシート、ドキュメントなど全部入りのビジネスソフトと言っていいでしょう

こちらの企業はTikTokでおなじみのバイトダンス（字節跳動）の子会社ということで、同社は海外向けやエンタープライズ向けの製品に力を入れたい意向があるそうです
G SuiteやOffice、Slackなどの競合に位置する気概のようですが、そういうバックグラウンドはいちエンジニアの端くれたる僕には関係なく、ともかく僕のチームで採用されていています
![img1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/cc4f9156-8e7d-a3f0-f8ee-351f1fac49dc.png)
[日本語公式サイト](https://www.larksuite.com/ja_jp/)より

できることが多彩で正直使い切れてないのですが、LarkではAppという形で外部と連携した機能を使えるようです。

Appにはチーム内で使用するCustom Appと、Larkユーザー全体に公開できるPublic Appとがあり、それぞれにまたGadget、Web page、Botという区分があります。よくわからない。
![img2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/d55c997d-9a34-9289-3128-2dc4a9963259.png)
![img3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/13e393b2-873e-37cc-e2aa-dde8ca2af841.png)
[Lark Developer - Open Platform Overview](https://open.larksuite.com/document/ukzMxEjL5MTMx4SOzETM/ukDNwYjL5QDM24SO0AjN)



#今回つくるもの
その中で今回は、特定のチャットにメッセージを送る機能を用いたいと思うのでCustom AppのBotにあるメッセージ機能を使います

どこかの環境にBotサーバーを設置し、それを呼び出すことでLarkのチャットにメッセージが送られるという単純な仕組みにしたいと思います。

メッセージを受け取って返答したり、そのほかLark内のイベントに応じたアクションなどBotの機能は数多くありそうですが、今回は単純な送信のみに対応したいと思います。

実行環境はGoogleのCloud Functions上ですが、Node.jsを用いるのでGCEやAWSのEC2, Lambdaなど他の環境でも参考にできると思います。

サーバー自体のリクエスト処理などは今回の範疇外とさせていただきます。上記の通り環境にもよりますし、ただでさえ冗長なのと、何より初めてQiitaを書く僕が限界です。
※一応[こちらのページ](https://open.larksuite.com/document/ukzMxEjL5MTMx4SOzETM/uAzNwYjLwcDM24CM3AjN)にPythonでの[完全なサンプル](https://sf16-muse-va.ibytedtos.com/obj/website-oversea/093b18e5189ad423a378b616b1bf94c3_QwxpZBDZc9.py)がありますが説明等はなく、コメントも中国語ですので自信のある方はお使いください。ほんとは解説したい。

#Lark botの設定
[公式ドキュメント](https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/uUjMyEjL1IjMx4SNyITM)にしたがって進めます。サンプルやチュートリアルではないのでサーバーなどの話は載っていません。
![img4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/8f4392d4-9043-248a-564a-ca037901d0b6.png)



4つのプロセスに分かれていますが初めの2つだけで今回は実装できます

####AppとBotの設定

Create a Bot in LarkではLarkの開発者向けサイトからAppを追加し、Botを設定する手順が記載されています。直接コーディングなどはしませんが大まかに解説します。

>See [Create a custom app](https://open.larksuite.com/document/ukzMxEjL5MTMx4SOzETM/uEjNwYjLxYDM24SM2AjN) to Create an app for your business.

とのことなのでそこの解説です。公式のガイドに従えば特に支障なくすすめます。

Lark Developerの各ページから右上の[My Apps](https://open.larksuite.com/app)に移動し、Create an Appから必要な項目を入力してCustom Appを追加します。

それからApp Capabilityを有効化する必要があるので先ほど追加したAppの詳細を表示し、FeaturesからBotを有効化します。Permissionなどの指示もありますが不要です。

最後に、自分が使っているLarkからBotを見つけて使用するためにReleaseしてVersionを1度追加する必要があります。Releaseを行っても使用しているチームの外には見えないのでVersion Management & Releaseから適当に項目を入力してください。（チームの権限者から承認されるとチャットに追加できるようになります。）

####メッセージ送信の要件
Bot development processに戻りましょう。Appの設定ができたので次のSend messagesに進みます。

>Step 1: Obtain the API access token
>See [Obtain access token](https://open.larksuite.com/document/uMDNxEjLzQTMx4yM0ETM/ukjMyEjL5IjMx4SOyITM).
>
>Step 2: Invoke the message sending API
>Send messages to users/group chats as an app. For the message API, see [Send a message](https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/ugDN0EjL4QDNx4CO0QTM).
>
>* Send a private message: Send a message to a user within the app visibility range. A user must have an access visibility to that app.
>
>* Send group messages: Send messages within a group. The app needs to be in the corresponding group, see [Using bots in groups](https://open.larksuite.com/document/uMDNxEjLzQTMx4yM0ETM/uUjMyEjL1IjMx4SNyITM#Using%20bots%20in%20groups).

といった形で2ステップ書かれています。Botからメッセージを送るにはアクセストークンが必要で、Step1ではその手法が書いてあります。Step2ではテキストメッセージの送信とその際に必要な情報が載っています。

先ほどのガイドに従ってもいいのですが、リンクのみの箇所が多いため直接[リファレンス](https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/ugjMyEjL4IjMx4COyITM)を参照することをお勧めします

まとめると

1. Appの詳細から`app ID`と`app secret`を取得する（初回のみ）
2. IDとsecretでAPIを呼び出しAPIから`chat_id`を取得する（初回のみ）
3. 同様に、`tenant_access_token`を取得する
4. `chat_id`と`tenant_access_token`と一緒にメッセージをリクエスト

といった流れで実装できます
1.と2.は変更されないのでbotサーバーの処理として行う必要はありません。ただし2.を呼び出すためには3.が必要なので前準備でもサーバーでも実行します。

ややこしいですが2.と3.を一度ターミナルのコマンドなどで済ませ、3.と4.をサーバーの処理として実装しましょう

#実装

####コマンドから認証情報を取得 (1.と3.の部分)
APIを呼び出すので任意のHTTPリクエストができるツールで構いませんがここでは`curl`を用います

リクエストを送信するのに必要な1.の情報は先ほどの[My Apps](https://open.larksuite.com/app)の該当アプリ設定ページから取得します
Credentials & Basic Infoの一番上に`App ID`と`App Secret`が載っているので、下記コマンドの****に置き換えてください(この情報は後のサーバーコードでも使用します)

参考 : Reference > Getting Started > Authorization > [Obtain tenant_access_token (internal apps)](https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/uUjN0EjL1YDNx4SN2QTM)

```
$curl -XPOST -H 'Content-Type:application/json' -d '{"app_id":"*********","app_secret":"*********"}' https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal/
```

これで`tenant_access_token`が入手できるので、次のコマンドにて使用します

####コマンドから`chat_id`を取得 (2.の部分)
同様に`tenant_access_token`を用いることで`chat_id`が入手できます

参考 : Reference > Bot > Group (Bot) > [Obtain group list](https://open.larksuite.com/document/uMzMyEjLzMjMx4yMzITM/uETM1EjLxETNx4SMxUTM)

```
$curl -H 'Authorization:Bearer *********'  https://open.larksuite.com/open-apis/chat/v4/list
```

整形しないと大変ですが以下の画像のようなレスポンスが得られます。

![img10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185791/f69f8374-4784-97c8-7ef2-8d3d28def128.png)


この場合2つのチャットに参加しており、それぞれの情報が取得されています。チャット名は`name`の部分に書かれていますが日本語の場合はコードに変換されているので探しにくいかもしれません。（背景は実際のLarkの画面です）

もし参加しているチャットが多い場合`has_more`の部分が`true`になり、表示されません

ここで取得した、メッセージを送りたい目的のチャットの`chat_id`を後ほど使用します

#Nodeでメッセージを送信
やっと必要な情報が揃ったので実際のコードに移ります。メッセージ送信の要件にあった3.4.を書きましょう。

axiosを用いていますがこちらも好きなHTTPクライアントで構いません

まずコマンドでも行った3.の認証情報の取得を関数にまとめます。同様に、`app_id`と`app_secret`を用いてリクエストを送る処理です。単純なトークンを返すようにしましょう。

```index.js
async function getTenantToken(app_id, app_secret) {
    const reqConf = {
        "conf": { "headers": { "Content-Type": "application/json" } },
        "data": {
            "app_id": app_id,
            "app_secret": app_secret,
        }
    }

    return await axios.post('https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal/', reqConf.data, reqConf.conf)
        .then(function (response) {
            return response.data.tenant_access_token
        })
        .catch(function (error) {
            console.log(error);
        });
}
```

2.は先ほど用意した`chat_id`で良いので4.の処理です。こちらも情報さえあればPOSTリクエストを送るだけで可能なので簡単ですね。

```index.js
async function sendMessageToLark(tenant_access_token, chat_id, message) {
    const reqConf = {
        "conf": {
            "headers": {
                "Content-Type": "application/json",
                "Authorization": "Bearer " + tenant_access_token,
            }
        },
        "data": {
            "chat_id": chat_id,
            "msg_type": "text",
            "content": {
                "text": message
            }
        }
    }

    await axios.post('https://open.larksuite.com/open-apis/message/v4/send/', reqConf.data, reqConf.conf)
        .then(function (response) {
            return
        })
        .catch(function (error) {
            console.log(error);
        });
}
```

上記の関数を実行すれば実際にLarkでメッセージを受け取れるでしょう。当然ですがIDやトークンなどセキュリティに関わる情報は直に記入せず環境変数などで秘匿化してください。

```index.js
// さっきのchat_id
const chat_id = ************
const app_id = ************
const app_secret = ************
const message = "Lark Botからメッセージです"

getTenantToken(app_id, app_secret).then((r) => {
    sendMessageToLark(r, chat_id, message)
}).catch(e => console.log(e))
```

#まとめ
以上でLark Botから単純なメッセージの送信が実装できました。送信の部分は既存のチャットBotとあまり違いがなく、ほとんどがLarkの設定など固有の要素の理解が重要でしょうか。
実際ぼくも情報がない中で苦労したので少しでも参考になれば、と思います。

ドキュメントを見て分かる通り他にもLarkには多くの機能が存在します。チャットBotにしてもそもそもメッセージを受信するところから作れますし、Larkはそれだけでアプリ一つが成り立つようなかなり大規模なプラグインもできるようです。

まだまだ日本では途上にあるサービスなので、今後の動向を注視したいですね。