📨

Lark API実践:TypeScriptでBotからメッセージ送信してみる

に公開

はじめに

こんにちは、株式会社ゼンビットのゆうとです。

今回は社内で使っているオールインワンツール「Lark」のAPIを使い、Botからメッセージ送信する方法を紹介します。
Larkのドキュメントが現時点で少ないため、実装の参考になればと思います。

対象読者

  • LarkでAPIを利用したい方
  • TypeScriptでLark API送信を構築予定の方

記事で説明する内容

  1. Lark APIの利用方法
  2. Larkコンソール画面でのテスト方法
  3. TypeScriptでのBotメッセージ送信実装

説明しない内容

  1. TypeScript部分の構築詳細
  2. Lark、axiosの基本的な説明

背景

社内システムで「○○したらチャット通知したい」という要件がありました。
Larkのドキュメント少なくて苦戦するかと思いましたが、意外とスムーズに実装できました。
このコツを掴めれば、チャットだけではなくLark API全体に応用効きそうです。


構成など

  • Node.js 18 以上

コンソール画面操作

TypeScriptの構築を行う前に、コンソール画面でBot作成などの下準備を行います。
後述のAPI Explorerパネルが非常に便利です。

Bot作成 + チャットグループご招待

まずはLarkの開発者コンソールで「Create Custom App」をクリック。
お好きなお名前、説明、アイコンのAppを作成します。
bot
Botですしロボアイコンでしょう!

作成後、Appの編集画面に遷移します。初期表示時に機能の追加を促されます。
今回Botにチャット送信してもらいたいのでBotを選択。
addFeatures

Botを有効化できました。ページを翻訳したところ、バージョン更新しないとAppが有効化できないようでした。
versionDescription

有効化した後、LarkデスクトップアプリからBot名で検索するとBotがヒットするようになりました。
search

Botにチャットしてもらいたいので、グループチャットにご招待します。
Larkデスクトップアプリからグループチャットを開いて、右上の「・・・」を押して「設定」を開きます。
そうすると「ボット」の項目があります。こちらから追加します。
botAdd

Bot追加できました。
chatGroup

scope追加 + 開発者コンソール画面での動作確認

メッセージ送信などのAPIを実行するには、Appにscope(権限)を付与する必要があります。

まずはLark APIが何できるのか公式をチェックし、メッセージ送信のAPIを見つけます。
https://open.larksuite.com/document/server-docs/getting-started/server-api-list
https://open.larksuite.com/document/server-docs/im-v1/message/create?appId=cli_a824d841f6b85e1c

具体的なscope明記はされていませんね。
「文脈から見てim:messageを入れればいいのかな?」と思ったところ、右ペインになにやら操作出来そうな画面があります!
messageRequest

Scopesタブを開くとメッセージ送信に必要なscope情報が書いてあります。
larkScopes
これは非常に便利ですね!以降API Explorerパネルと呼びます。

それぞれの説明は以下の通りです。

  • API scopes-Tenant token scopes: API実行に必須
  • Field scopes-Tenant token scopes: 必須ではない。scopeがないとレスポンスが一部隠蔽されます。
  • Approval required=Yes の場合、管理者承認が必要です。

「API scopes」に該当する必要なscopeを全てチェックして、「Add in bulk」にすれば準備完了です。
このままAPI Explorerタブから操作してみます。
apiExplorer
以下の流れのようです。

  1. トークンを取得
  2. 対象チャットIDを設定
  3. API送信

案内通りに操作してみるとチャット送信できました!

コーディング

ここからは、TypeScriptでBotによるメッセージ送信を実装してみます。
前述のAPI Explorerタブでの操作例を参考に、トークン取得⇒API送信といった流れになります。
必要なのは axioszod のみです(もちろん Node.js 環境は前提)。

クライアント + トークン取得作成

まずクライアント作成します。
トークン取得はクライアントの中に含めます。

api/client.ts
import axios from "axios";
import { getTenantAccessToken } from "./auth";

let token: string | null = null;

export async function getClient() {
  if (!token) token = await getTenantAccessToken();
  const instance = axios.create({
    baseURL: "https://open.larksuite.com/open-apis",
    headers: { Authorization: `Bearer ${token}` },
  });
  instance.interceptors.response.use(
    (response) => {
      console.log("Axios Response:", {
        status: response.status,
        data: response.data,
      });
      return response;
    },
    (error) => {
      console.error("Axios Error:", {
        status: error.response?.status,
        data: error.response?.data,
        message: error.message,
      });
      return Promise.reject(error);
    }
  );

  return instance;
}

次にトークン取得APIを作成します。
こちらを参照しながら必要なHTTP URL、Request header、Request bodyを定義します。
https://open.larksuite.com/document/server-docs/getting-started/api-access-token/auth-v3/tenant_access_token_internal

LARK_APP_IDとLARK_APP_SECRETの値はこちらから取得しましょう。
token

api/auth.ts
import axios from "axios";

import { TenantAccessTokenResponse } from "../schemas/response";
export async function getTenantAccessToken(): Promise<string> {
  const res = await axios.post(
    "https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal",
    { app_id: process.env.LARK_APP_ID, app_secret: process.env.LARK_APP_SECRET },
    { headers: { "Content-Type": "application/json" } }
  );
  const data = TenantAccessTokenResponse.parse(res.data);
  if (data.code !== 0 || !data.tenant_access_token) {
    throw new Error(`Lark auth error: ${data.msg}`);
  }
  return data.tenant_access_token;
}

APIのレスポンスはZodでスキーマ定義します。

schemas/response.ts
import { z } from "zod";

export const TenantAccessTokenResponse = z.object({
  code: z.number(),
  msg: z.string(),
  tenant_access_token: z.string().optional(),
  expire: z.number().optional(),
});
export type TenantAccessTokenResponse = z.infer<
  typeof TenantAccessTokenResponse
>;

メッセージ送信

次にメッセージ送信する部分です。
こちらも引き続き、公式記載の通りHTTP URL、Request header、Request bodyを定義します。
https://open.larksuite.com/document/server-docs/im-v1/message/create?appId=cli_a824d841f6b85e1c

api/message.ts
import { getClient } from "./client";
import { SendMessageResponse } from "../schemas/response";
export async function sendMessage(chatId: string, message: string) {
  const client = await getClient();
  const res = await client.post(
    "/im/v1/messages",
    {
      receive_id: chatId,
      msg_type: "text",
      content: JSON.stringify({ message }),
    },
    {
      params: { receive_id_type: "chat_id" },
    }
  );

  const data = SendMessageResponse.parse(res.data);
  if (data.code !== 0) throw new Error(`Lark message error: ${data.msg}`);
  console.log("Message sent:", message);
}

こちらもスキーマ定義します。オブジェクトの中身は特に必要ない形にしています。
メッセージ送信のレスポンスなので、エラー取得できれば問題ないかと思いました。

schemas/response.ts
import { z } from "zod";

export const TenantAccessTokenResponse = z.object({
  code: z.number(),
  msg: z.string(),
  tenant_access_token: z.string().optional(),
  expire: z.number().optional(),
});
export type TenantAccessTokenResponse = z.infer<
  typeof TenantAccessTokenResponse
  >;

+export const SendMessageResponse = z.object({
+  code: z.number(),
+  msg: z.string(),
+  data: z.object({}).optional(),
+});
+export type SendMessageResponse = z.infer<typeof +SendMessageResponse>;

実行

最後にテスト用のスクリプトを作ります。
TEST_CHAT_IDはAPI Explorerパネルから取得してしまいます(APIで取得することも可能です)。
getChatid

scripts/sendtest.ts
import { sendMessage } from "../api/message";

async function main() {
  const message = `テストです`;
  await sendMessage(process.env.TEST_CHAT_ID, message);
}

main();

実行します。
terminalSuccess

チャット送信できました🎉
success

結論・まとめ

Lark APIを利用してTypeScriptからメッセージ送信することができました。
API Explorerパネルが非常に便利といった感想です。

今後の発展

  • serverAPIListのAPIであれば同じような手順でAPI利用することが出来そうです。

全体を通しての感想

  • Lark最高!!!!

Discussion