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でそれぞれ必要な処理を呼び出すようにした。

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 Zoom予約レコード

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


この記事が気に入ったらサポートをしてみませんか?