Google Apps ScriptでWebex APIを操作する【GAS】
コロナ渦を経て、日本の企業でもはやWeb会議システムを導入していない企業はどうなのよ?と言われるほどもはや当たり前の存在になりました。圧倒的にZoomがシェアを握っていて、TeamsやGoogle Meetがそれを追いかける状況で、結構しっかり作られていてREST APIも豊富なのに知名度低いのが「Cisco Webex」。
無償で利用できるのに、Ciscoがあまり推していないのかシェアはいまいちですが、自分は仕事で使っています。ということで今回はこのWebex APIをGoogle Apps Scriptから使ってみました。
目次
今回利用するスプレッドシート等
- Webex API - Google Spreadsheet
- Webex API Reference
上記のリファレンスがサンプルコードやレスポンス、デモが整っているのですが、もう一つリファレンスがあり、後者のほうが実は全てを網羅してたりします(IP電話/クラウドPBXのWebex Callingなどの記載もきっちり乗っています)。今回はこのAPIのOAuth2.0認証およびWebhookを設置するコードを作ってみました。
Webhookの受け口もGASで記述して取得させています。
※自分の場合、Webex Callingの着電をWebhookで取得したかったために今回のコードを作成しています。PCやスマフォ、実機から電話の出来るためコールセンターに向いています。
事前準備
今回のプログラムはOAuth2認証、Webhook設置、Wehookの受け口の3つをGASで作ります。デバッグ含めてポイントがいくつかあるので、以下の作業をしておきましょう。
GAS側の作業
プロジェクトを移動する
ログエクスプローラの使い方等は以下のエントリーを参照してください。
図:プロジェクト変更画面
デプロイを行う
GAS側で処理するためのコードを記述したら、doPostの受け口を有効化するためにデプロイをします。
- スクリプトエディタを開く
- 右上のデプロイをクリック
- 新しいデプロイをクリック
- 種類の選択ではウェブアプリを選択し、次のユーザとして実行は自分にしておきます。
- アクセスできるユーザは、全公開する必要があるので「全員」としておきます(Webhookなので認証有りだとWebex側からアクセス出来ない)
- 末尾がexecで終わるURLが発行される。これを次項のGASのWebhookurlとして指定します。
- 次回以降コードを編集して再デプロイ時はデプロイを管理から同じURLにて、新しいバージョンを指定して発行することが出来ます。
コールバックURLを取得する
コールバックURLとは、認証を完了しAccess Tokenを取得したら戻るべきURLを指定するものです。これは、スクリプトIDをもとに作られているので、スクリプトIDを取得して組み立てます。
- スクリプトエディタのサイドバーより「プロジェクトの設定」を開く
- 情報の中にある「スクリプトID」を控えておく。
- https://script.google.com/macros/d/スクリプトID/usercallback として組み立てる。これがコールバックURLとなる。
図:スクリプトIDはファイル毎に異なるのです。
ライブラリの追加
以下の手順でOAuth2 library for Google Apps Scriptライブラリを追加しましょう。
- スクリプトエディタを開きます。
- サイドバーよりより「ライブラリ」の+ボタンをクリック
- ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
- 今回はバージョンは43を選択してみます。
- 保存ボタンを押して完了
これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。
図:ライブラリを追加した様子
Webex側での作業
OAuth2.0認証に必要なクライアントIDやシークレットを取得する為に、Webex for Developerにログインしてアプリを作成します。
- Webex for DeveloperのCreate New Appにログインする
- Create an Integrationをクリック
- Integration Nameがアプリの名前になります。
- アイコンは512x512のサイズのpng画像をアップする必要があります。
- App Hub Descriptionがアプリの説明文
- Redirect URLは、前述のコールバックURLを入れます。
- ScopesはAPIを利用して何をするかでオンにしていきます。今回は適当に「spark-compliance:webhooks_read spark:kms spark-compliance:webhooks_write meeting:schedules_read」をオンにします。
- Add Integrationをクリックすると、Client IDおよびClient Secretが手に入ります。
図:IDとSecretを後で利用します。
ソースコード
今回のコードはMeetingをスケジュールしたらWebhookで飛ばすのを設置します。そのWebhookはdoPostで受け取る必要があります。また、OAuth認証ではScopeが必要になります。あとはScopeに応じてWebexの様々なAPIを叩くことが出来るようになります。
OAuth2認証コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
//認証用の各種変数 var appid = 'ここにクライアントIDを入れる'; var appsecret='ここにクライアントシークレットを入れる'; var authurl = "https://webexapis.com/v1/authorize?" var tokenurl = "https://webexapis.com/v1/access_token" //スコープを半角スペース区切りで入れる var scope = "spark-compliance:webhooks_read spark:kms spark-compliance:webhooks_write meeting:schedules_read" function startoauth(){ //UIを取得する var ui = SpreadsheetApp.getUi(); //認証済みかチェックする var service = checkOAuth(); if (!service.hasAccess()) { //認証画面を出力 var output = HtmlService.createHtmlOutputFromFile('template').setHeight(450).setWidth(500).setSandboxMode(HtmlService.SandboxMode.IFRAME); ui.showModalDialog(output, 'OAuth2.0認証'); } else { //認証済みなので終了する ui.alert("すでに認証済みです。"); } } //アクセストークンURLを含んだHTMLを返す関数 function authpage(){ var service = checkOAuth(); var authorizationUrl = service.getAuthorizationUrl(); var html = "<center><b><a href='" + authorizationUrl + "' target='_blank' onclick='closeMe();'>アクセス承認</a></b></center>" return html; } //認証チェック function checkOAuth() { return OAuth2.createService("webex") .setAuthorizationBaseUrl(authurl) .setTokenUrl(tokenurl) .setClientId (appid) .setClientSecret(appsecret) .setCallbackFunction ('authCallback') .setPropertyStore (PropertiesService.getUserProperties()) .setScope(scope); } |
- クライアントIDとシークレットを記述しておきます。
- Scopeは半角スペースで区切って複数指定することが可能です。
- ライブラリで認証の一連の流れを作っておきます。
- 認証が成功するとAccess Tokenが「ユーザプロパティ」に格納されます。
図:認証を実行中
Webhook設置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
//webhookurl let targetUrl = "ここに公開したウェブアプリケーションURLを記入する"; //webhook making function createWebhook() { //ui let ui = SpreadsheetApp.getUi(); //プロパティ let prop = PropertiesService.getScriptProperties(); let webhookid = prop.getProperty("webhookid"); //トークン確認 var service = checkOAuth(); //認証チェック if(service.hasAccess()) { //webhookidがある場合はまずdelete if(webhookid == "" || webhookid == undefined){ //スルーする }else{ //webhookidを削除する let ret = deleteWebhook(); switch(ret){ case 0: //削除完了してるのでスルー break; case 1: //削除時エラー ui.alert("削除リクエスト時にエラー"); return; case 2: ui.alert("認証が実行されていませんよ。"); break; } } //リクエストヘッダ let header = { "Authorization": 'Bearer ' + service.getAccessToken(), "Content-Type" : "application/json" } //リクエストボディ let payload = { name : "m_webhook", targetUrl : targetUrl, resource : "meetings", event : "created" } //リクエストオプション let options = { method: "POST", headers: header, payload : JSON.stringify(payload), muteHttpExceptions: true } //エンドポイントURLを構築 let endpoint = "https://webexapis.com/v1/webhooks"; //レスポンスデータ var response = UrlFetchApp.fetch(endpoint, options); //リクエスト結果を取得する const result = JSON.parse(response.getContentText()); //idを追加する prop.setProperty("webhookid",result.id) //レスポンスコードを取得する let status = response.getResponseCode(); //レスんポンスコードで判定 if(status == 200){ //削除完了 ui.alert("Webhookを設置完了しました。"); return 0; }else{ ui.alert("webhook設置に失敗しました。"); } }else{ ui.alert("認証が実行されていませんよ。"); } } //webhookを削除する function deleteWebhook(){ //トークン確認 var service = checkOAuth(); //プロパティ let prop = PropertiesService.getScriptProperties(); let webhookid = prop.getProperty("webhookid"); if(service.hasAccess()) { //リクエストヘッダ let header = { "Authorization": 'Bearer ' + service.getAccessToken(), } //リクエストオプション let options = { method: "DELETE", headers: header, muteHttpExceptions: true } //エンドポイントURLを構築 let endpoint = "https://webexapis.com/v1/webhooks/" + webhookid; //レスポンスデータ var response = UrlFetchApp.fetch(endpoint, options); //リクエスト結果を取得する const result = response.getContentText(); //レスポンスコードを取得する let status = response.getResponseCode(); //レスんポンスコードで判定 if(status == 200 || status == 201 || status == 204){ //削除完了 prop.setProperty("webhookid",""); return 0; }else{ return 1; } }else{ //認証されていない場合 return 2; } } |
- targetUrlには前述の準備で取得しておいたデプロイした末尾がexecのURLを入力します。
- Webhookは作ったら有効になりますが、作りっぱでは次に作ったときにも前のものが残り続けてしまいます。ので、deleteするコードも必要になります。
- webhookを作ったらそのIDをプロパティに格納しておき、削除時にもそれを使って削除を行います。
- payloadの中身が最低限必要なリクエストボディで、resourceがmeetingならWebex Meeting、telephony_callsがWebex Callingのイベント指定。これらのイベントで何かあるとWebhookが飛んでいく仕組みです。
- eventはcreatedが作成時や着信時、updateが更新時に発火するという指定になります。
- payloadはJSON.stringifyで括って送りつける必要があります。
Webhookの受け口
doPostのコード
doPostでなければWebhookとして受け取れません。また、前述でGCPのログエクスプローラを使う為の準備をしましたが、こうすることで、doPostで受け取ったデータをconsole.logでデバッグすることが出来ます。Webhookデータはe.postData.contentで取得する事が可能で、受け取ったデータを元に処理をすることが可能です。
1 2 3 4 5 |
//webhookurl function doPost(e){ let ret = e.postData.content; console.log(ret) } |
図:doPostの中身をデバッグ中
受信したWebhookの中身
e.postData.contentで内容を受け取ると今回はWebex Meetingでeventを指定してるので以下のような内容を取得出来ます。複数テナントがある場合には、組織のIDを元にWebhookの作成時にリクエストオプションとしてfilterにてorgId="組織のID"を加えておくと、Webhookを飛ばす内容を事前に絞り込めます。
また、今回のdoPostのURLは公開URLなので、事前にリクエストオプションにsecretにて値を入れておき、Webhookを設置。レスポンスデータ内にそれがあるかどうかをdoPost内のコードで判定して弾くようにすれば安心です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
{ "id":"WebhookのID", "name":"m_webhook", "targetUrl":"Webhookの受け口のURL", "resource":"meetings", "event":"created", "orgId":"Webhookを作った人の組織のID", "createdBy":"Webhookを作った人のID", "appId":"アプリの固有のID", "ownedBy":"creator", "status":"active", "created":"2023-03-17T11:54:16.362Z", "data":{ "id":"ミーティングの固有のID", "meetingNumber":"ミーティングナンバー", "meetingType":"ミーティングのタイプ", "timezone":"UTC", "start":"2023-03-18T12:05:00Z", "end":"2023-03-18T12:45:00Z", "hostUserId":"ユーザのID", "state":"active", "hostEmail":"ミーティングをセットした人のメアド", "siteUrl":"ミーティングのサイトURL", "orgId":"組織のIDがここに入ってくる", "hostType":"1001001" } } |
関連リンク
- Web会議システムのシェア・市場規模を解説!一番選ばれている人気サービスは?
- Webex で ChatGPT API を使った Bot の作り方
- Cisco WebexとAWS LambdaでOpenAI GPT-3のチャットボットを作る
- Cisco Webex TeamsのBotで、投稿されたメッセージにリアルタイムに反応する(4分で実装)
- Webex Calling APIを始めてみよう-3種類のAPIについて概要を把握する
- IOS/IOS-XE : TFTP/FTP/SCP を使用したファイル転送
- Cisco IOS - File System
- 【GAS】Googleカレンダから今日のイベントを拾ってWebex Teamsに通知する
- 【GAS】Googleスプレッドシートで管理している当番表をメンション付きでCisco Webex Teamsに定期的に通知する