Lark API実践:TypeScriptでBotからメッセージ送信してみる
はじめに
こんにちは、株式会社ゼンビットのゆうとです。
今回は社内で使っているオールインワンツール「Lark」のAPIを使い、Botからメッセージ送信する方法を紹介します。
Larkのドキュメントが現時点で少ないため、実装の参考になればと思います。
対象読者
- LarkでAPIを利用したい方
- TypeScriptでLark API送信を構築予定の方
記事で説明する内容
- Lark APIの利用方法
- Larkコンソール画面でのテスト方法
- TypeScriptでのBotメッセージ送信実装
説明しない内容
- TypeScript部分の構築詳細
- Lark、axiosの基本的な説明
背景
社内システムで「○○したらチャット通知したい」という要件がありました。
Larkのドキュメント少なくて苦戦するかと思いましたが、意外とスムーズに実装できました。
このコツを掴めれば、チャットだけではなくLark API全体に応用効きそうです。
構成など
- Node.js 18 以上
コンソール画面操作
TypeScriptの構築を行う前に、コンソール画面でBot作成などの下準備を行います。
後述のAPI Explorerパネルが非常に便利です。
Bot作成 + チャットグループご招待
まずはLarkの開発者コンソールで「Create Custom App」をクリック。
お好きなお名前、説明、アイコンのAppを作成します。
Botですしロボアイコンでしょう!
作成後、Appの編集画面に遷移します。初期表示時に機能の追加を促されます。
今回Botにチャット送信してもらいたいのでBotを選択。
Botを有効化できました。ページを翻訳したところ、バージョン更新しないとAppが有効化できないようでした。
有効化した後、LarkデスクトップアプリからBot名で検索するとBotがヒットするようになりました。
Botにチャットしてもらいたいので、グループチャットにご招待します。
Larkデスクトップアプリからグループチャットを開いて、右上の「・・・」を押して「設定」を開きます。
そうすると「ボット」の項目があります。こちらから追加します。
Bot追加できました。
scope追加 + 開発者コンソール画面での動作確認
メッセージ送信などのAPIを実行するには、Appにscope(権限)を付与する必要があります。
まずはLark APIが何できるのか公式をチェックし、メッセージ送信のAPIを見つけます。
具体的なscope明記はされていませんね。
「文脈から見てim:messageを入れればいいのかな?」と思ったところ、右ペインになにやら操作出来そうな画面があります!
Scopesタブを開くとメッセージ送信に必要なscope情報が書いてあります。
これは非常に便利ですね!以降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タブから操作してみます。
以下の流れのようです。
- トークンを取得
- 対象チャットIDを設定
- API送信
案内通りに操作してみるとチャット送信できました!
コーディング
ここからは、TypeScriptでBotによるメッセージ送信を実装してみます。
前述のAPI Explorerタブでの操作例を参考に、トークン取得⇒API送信といった流れになります。
必要なのは axios
と zod
のみです(もちろん Node.js 環境は前提)。
クライアント + トークン取得作成
まずクライアント作成します。
トークン取得はクライアントの中に含めます。
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を定義します。
LARK_APP_IDとLARK_APP_SECRETの値はこちらから取得しましょう。
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でスキーマ定義します。
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を定義します。
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);
}
こちらもスキーマ定義します。オブジェクトの中身は特に必要ない形にしています。
メッセージ送信のレスポンスなので、エラー取得できれば問題ないかと思いました。
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で取得することも可能です)。
import { sendMessage } from "../api/message";
async function main() {
const message = `テストです`;
await sendMessage(process.env.TEST_CHAT_ID, message);
}
main();
実行します。
チャット送信できました🎉
結論・まとめ
Lark APIを利用してTypeScriptからメッセージ送信することができました。
API Explorerパネルが非常に便利といった感想です。
今後の発展
- serverAPIListのAPIであれば同じような手順でAPI利用することが出来そうです。
全体を通しての感想
- Lark最高!!!!
Discussion