kintone-Zoom連携自習5日目
kintoneのWebhookにSAMで作成されるエンドポイントを登録する。Lambdaでミーティングの情報を取得してミーティングを作成する。
ミーティングが作成できたらミーティングIDをkintoneに登録する。
kintone Webhookの情報を取得する
kintoneのWebhookからレコードの情報が渡されるので、Lambdaで取得してクラスメンバにセットする。
Webhookから渡されたkintoneのレコード情報は文字列になっているので、JSONにパースする。
SAMで作成された雛形のpackage.jsonでは、ファイルのエントリーポイントは下記の通り`app.js`になっている。ビルド後にもろもろ必要なファイルがコンパイル・バンドルされる仕組み。
{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
"author": "SAM CLI",
"license": "MIT",
"dependencies": {
・・・
実際のコーディングするのはapp.ts。このファイルにLambdaのエントリーポイントが記述されている。下記の通り。
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
let response: APIGatewayProxyResult;
try {
console.log(event);
const kintone: any = new Kintone(event);
const webhookRequest = kintone.webhookRequest();
console.log(webhookRequest);
if (webhookRequest.type === 'ADD_RECORD') {
const accessToken: any = await Zoom.getAccessToken();
const ZoomMeetingCreateSetting = Zoom.createMeetingParam(webhookRequest);
const response = await Zoom.createZoomMeeting(accessToken.data.access_token, ZoomMeetingCreateSetting);
console.log(response);
}
response = {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
}),
};
} catch (err: unknown) {
console.log(err);
response = {
statusCode: 500,
body: JSON.stringify({
message: err instanceof Error ? err.message : 'some error happened',
}),
};
}
return response;
};
さらに、app.tsの `lambdaHandler`という関数が処理の最初のエントリーポイントになっているのは、SAMの設定ファイル`template.yaml`のHandlerに記述しているから。
ここを変更すれば名前はlambdaHandlerでなくても問題ない。
Resources:
ZoomIntegrationFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: zoom-integration/
Handler: app.lambdaHandler
Runtime: nodejs14.x
Environment:
Variables:
ACCOUNT_ID: ###########
・・・
ちなみに、Zoomの認証情報はLambdaの環境変数にセットするが、SAMではEnvironmentのVariablesに設定する。
Lambda側からは、AWSの環境変数としてprosess.env.~でアクセスできる。
取得したデータからZoomミーティングを作成する
とりあえずkintone側の処理とZoom側の処理のファイルを分割することにして、app.tsでそれぞれ必要な処理を呼び出すようにした。
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { Zoom } from './zoom';
import { Kintone } from './kintone';
/**
*
* Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
* @param {Object} event - API Gateway Lambda Proxy Input Format
*
* Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
* @returns {Object} object - API Gateway Lambda Proxy Output Format
*
*/
export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
let response: APIGatewayProxyResult;
try {
console.log(event);
const kintone: any = new Kintone(event);
const webhookRequest = kintone.webhookRequest();
console.log(webhookRequest);
if (webhookRequest.type === 'ADD_RECORD') {
const accessToken: any = await Zoom.getAccessToken();
const ZoomMeetingCreateSetting = Zoom.createMeetingParam(webhookRequest);
const response = await Zoom.createZoomMeeting(accessToken.data.access_token, ZoomMeetingCreateSetting);
console.log(response);
}
response = {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
}),
};
} catch (err: unknown) {
console.log(err);
response = {
statusCode: 500,
body: JSON.stringify({
message: err instanceof Error ? err.message : 'some error happened',
}),
};
}
return response;
};
zoom.ts
import axios from 'axios';
interface ZoomMeetingCreate {
topic: string;
start_time: string;
duration: number;
timezone?: string;
}
export class Zoom {
static createMeetingParam(kintoneWebhook: any) {
const kintoneRecord = kintoneWebhook.record;
const meetingParam: ZoomMeetingCreate = {
topic: kintoneRecord.topic.value,
start_time: kintoneRecord.start_time.value,
duration: kintoneRecord.duration.value,
timezone: 'Asia/Tokyo',
};
return meetingParam;
}
static async getAccessToken() {
try {
console.log(process.env);
const authorizationString = Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString(
'base64',
);
const axiosInstance = axios.create({
baseURL: 'https://zoom.us/oauth/',
headers: { Authorization: `Basic ${authorizationString}` },
});
const response = await axiosInstance.post(
`token?grant_type=account_credentials&account_id=${process.env.ACCOUNT_ID}`,
);
console.log(response);
return response;
} catch (error) {
console.log(error);
}
}
static async createZoomMeeting(accessToken: string, ZoomMeetingCreateSetting: ZoomMeetingCreate) {
try {
const axiosInstance = axios.create({
baseURL: 'https://api.zoom.us/v2',
headers: { Authorization: `Bearer ${accessToken}` },
});
const response = await axiosInstance.post(`/users/me/meetings?userId=me`, ZoomMeetingCreateSetting);
console.log(response);
return response;
} catch (error) {
console.log(error);
}
}
}
kintone.ts
interface WebhookApp {
id: string;
name: string;
}
interface WebhookRequest {
id: string;
type: string;
app: WebhookApp;
record: any;
recordTitle: string;
url: string;
}
export class Kintone {
private EventBody: any;
private WebhookRequest: WebhookRequest;
constructor(event: APIGatewayProxyEvent) {
this.EventBody = Kintone.webhookRequestParse(event);
this.WebhookRequest = {
id: this.EventBody.id,
type: this.EventBody.type,
app: this.EventBody.app,
record: this.EventBody.record,
recordTitle: this.EventBody.recordTitle,
url: this.EventBody.url,
};
}
public webhookRequest = () => {
return this.WebhookRequest;
};
static webhookRequestParse(event: APIGatewayProxyEvent) {
return JSON.parse(event.body);
}
}
ビルド、デプロイしてkintoneのレコードを登録したら、Zoomミーティングが作成されることを確認する。
kintoneにZoomミーティング情報を登録する
作成したZoomミーティングにはミーティングID、招待URL等があるのでそれをWebhookが発火したkintoneのレコードに登録する。
kintoneのレコード更新にはkintone-rest-api-clientライブラリを利用する予定だったが、実行時エラーが発生するのでAxiosを利用。
Zoom Meeting API の Create Meeting
Zoomミーティング作成が成功するとレスポンスにZoom Meeting IDや招待URL等がセットされるので、それをkintoneのレコードに登録する。
kintoneIntegration.ts(※kintone.tsからファイル名を変更)
import axios from 'axios';
・・・
type UpdateZoomMeetingParam = {
meetingId: { value: string };
join_url: { value: string };
password: { value: string };
};
interface kintoneUpdateRecord {
app: string;
id: string;
record: UpdateZoomMeetingParam;
}
・・・
export class KintoneIntegration {
private EventBody: any;
private WebhookRequest: WebhookRequest;
private _kintoneUpdateRecordParam: kintoneUpdateRecord;
constructor(event: APIGatewayProxyEvent) {
this.EventBody = KintoneIntegration.webhookRequestParse(event);
this.WebhookRequest = {
id: this.EventBody.id,
type: this.EventBody.type,
app: this.EventBody.app,
record: this.EventBody.record,
recordTitle: this.EventBody.recordTitle,
url: this.EventBody.url,
};
this._kintoneUpdateRecordParam = {
app: this.EventBody.app.id,
id: this.EventBody.record.$id.value,
record: {
meetingId: { value: '' },
join_url: { value: '' },
password: { value: '' },
},
};
}
get webhookRequest(): WebhookRequest {
return this.WebhookRequest;
}
static webhookRequestParse(event: APIGatewayProxyEvent) {
return JSON.parse(event.body);
}
zoomMeetingParam(zoomMeetingParam: UpdateZoomMeetingParam) {
this._kintoneUpdateRecordParam.record.meetingId.value = zoomMeetingParam.meetingId.value;
this._kintoneUpdateRecordParam.record.join_url.value = zoomMeetingParam.join_url.value;
this._kintoneUpdateRecordParam.record.password.value = zoomMeetingParam.password.value;
}
get kintoneUpdateRecordParam(): kintoneUpdateRecord {
return this._kintoneUpdateRecordParam;
}
async updateRecord() {
try {
const axiosInstance = axios.create({
baseURL: 'https://luck-gk.cybozu.com',
headers: { 'X-Cybozu-API-Token': process.env.KINTONE_API_TOKEN },
});
const response = await axiosInstance.put(`/k/v1/record.json`, this.kintoneUpdateRecordParam);
// console.log(response);
return response;
} catch (error) {
console.log(error);
}
}
}

とりあえずkintoneにレコード登録して、Webhook発火時にレコードの内容でZoomミーティングを作成。Zoomミーティング作成後にミーティングの情報をレコードに登録するまで作成した。
続きます。