こんにちはJuneです。年明け早々コロナが本気出してきて、ますます外に出れない日々が続きます。まあエンジニアは家にいてもプログラムでいろんなもの作れるのでいい暇つぶしになります。そこでコロナで株が爆上がりのzoomにAPIがあるということを知って、早速使ってみました。会社でも「zoom APIには金の匂いがする!」とみんなで盛り上がったので是非探索してみます。
zoom API自体は2018年ごろから出ていたらしく、現在はv2がリリースされています。ドキュメントを一通り読んだ所、zoomで行えることは一通りAPIを通じて行えるそうです。zoom APIについてわかった箇所を詳細に説明したいですが、この記事では実際の活用例を説明したいので部分省略します。
しかし、zoom APIを使用する認証フローや仕組みについては簡単に解説します。その説明から始めますので、「webアプリのはよ動き見せろや!」「そんなの知っとんじゃ!」という方は「Laravelを立ち上げる」から読み始めてください。
詳しくはZoom API レファランスを見ればわかりますが、zoomのGUIでできることは基本的に可能です。ミーティングを作ったり、ウェビナーを開催したり、ユーザー情報を取ったり、開催中のzoomに対してチャットを送るなどなんでもできます。他にもwebhookや独自のコードでブラウザ上に映像・音声を出力できるSDKやエンドポイントもあるみたいです。
これらのAPIはRestAPIであり、特定のルートに対してGET/POST/PUT/DELTEでリクエストし、付属の情報はGETパラメータやPOSTパラメータで送信します。
参考:https://marketplace.zoom.us/docs/api-reference/introduction
zoom APIにアクセスするには、JWTとOAuth2.0が使用できます。二者の違いはアクセスできる機能の量とマーケットプレイスに公開できるかが主になります。
ます。JSONで認証情報をやりとりします。
JWTによる認証はOAuthで使用できる権限範囲より狭く、自分自身で簡単にライトに使用したい場合に使うらしいです。また開発したzoom アプリはマーケットプレイスを通じて公開することができますが、JWT認証の場合はその公開ができません。
独自のwebアプリとZoomを連携させたい場合は次のOAuth2.0認証をお勧めします。
参考:https://marketplace.zoom.us/docs/api-reference/using-zoom-apis#using-jwt
OAuthは自身のwebサービスの資格情報を第三者のサービスへ提供する際に使用される認証フローです。ここでいう「webサービス」は「zoom」で「第三者のサービス」は「私のLaravelアプリ」です。
つまりOAuth認証を用いることで「私のLaravelアプリ」は連携させた人の「zoom」の資格情報を利用できる様になります。よって「私のLaravelアプリ」は連携させた人のzoomのミーティング情報を読み取ったり、作成したり、ユーザー情報を取得することができます。
資格情報を与え、操作権限も付与するのでかなり厳重な認証システムが必要となりそこで、秘密鍵や特定のプロトコルなどを用いたOAuthが使用されます。上記のJWTよりセキュアであり、また様々なAPIを利用できる様になります。
OAuthで実装したzoom APIはマーケットプレイスに出して公開することができます。今回の説明ではこのOAuth認証をLaravelを用いて実装していきます。まだ認証・連携のイメージがつかないと思いますが、読んでいくうちにわかると思います(多分)。
参考:https://marketplace.zoom.us/docs/api-reference/using-zoom-apis#using-oauth
OAuthでの認証は以下の様に行われます。
もう少し具体的に解説すると
こちらも後で実際の画面のスクショ付きで解説しますので、今は「ヘェ〜」程度の理解で大丈夫です。
OAuthでアクセストークン を取得したら、そのトークンをリクエストヘッダーに仕込んでAPIにアクセスします。
まずアプリの機能と概要を説明しておきます。今回作るアプリは「匿名ユーザーが入力したフォームの内容に応じてzoomミーティングが作成され、それを管理できるwebアプリ」です。見た目は以下の感じです。
アプリを管理し連携させるzoomアカウントを持っている「管理者」と、フォームに入力する「匿名ユーザー(お客さん)」が存在するとします。
場面としては「zoomでのご相談はこちら」的な会社用のフォームであると思ってください。最初にお客さんはフォームにて名前・アドレス、zoomの希望開始時間を入力します。
フォーム入力内容が正しければ、zoom APIを通じて指定の時間で始まるzoom ミーティングを連携先のアカウントで作成。
APIからのレスポンスよりミーティングURLを取得し、DBに保存すると共にお客さんへミーティング情報をメールで飛ばす。(メールはローカルの環境でできなかったので、実装は割愛。ソースはあります。)
管理者は管理画面にて誰が・いつミーティングを開くのかを一覧で確認できる。またお客さんは時間変更用・削除用URLにアクセスして時間の変更・ミーティングのキャンセルが可能。
以上の様な機能を持たせたいと思います。とりあえずこれらの機能を実装する程度なので、厳密なバリデーションや細かい機能は割愛します。
私が慣れているLaravel 6を用いて作成します。Laravelのインストール方法は省略します。一応こちらで作ったDocker開発環境を用いています。またzoomへのHTTPアクセスをよく行うので、PHPのHTTPライブラリであるguzzlehttpをインストールしてください。
また、以下詳細な開発環境情報です。開発したリポジトリも開放してますのでぜひどうぞ。
それでは初めていきましょう。Laravelを実装する前に自身のZoomアカウントにて、zoomアプリを作成していきましょう。zoom マーケットプレイスへ移動します。そして画面上部の「Develop」をクリックし「Build App」をクリックします。
するとzoomアプリを選択する以下の様な画面が表示されますので、「OAuth」の「Create」をクリック
「Create」を押すとアプリの名前などを入力するモーダルが出現します。任意の名前を入力し、アプリのタイプ、マーケットプレイスに公表するかを決定します。とりあえず私は以下の様にしました。
名前とマーケットプレイスへの公開は後から変更できます。ひとまず入力したら「Create」を押します。
作成後にはアプリの管理画面に飛ばされると思います。そこから作成したアプリを選択して「App Credentials」を選択
Larvel側には 「Client ID」「Client Secret」 が必要となります。後で.env
ファイルに記載します。ちなみに 「Client Secret」 は絶対に外に漏れてはいけません。
「Redirect URL for OAuth」 にて承認画面からのリダイレクト先を指定しておきます。承認画面でアプリ連携を許可した際にはトークンなどが 「Redirect URL for OAuth」 あてへ送信され、ユーザーもリダイレクトされます。ここの値が異なっているとエラーで認証が進みません。
今は開発環境なのでドメインにlocalhost
を指定しています。(私の環境ではlocalhost:9000
でLaravelの画面が表示される様に設定しています。php artisan serve
などの場合はlocalhost:8000
になると思いますので、ポートの指定に気をつけてください。)
「whitelist URL」はOAuthのリダイレクト先として許可するURLを指定できます。OAuthリダイレクトのURLに完全一致させるか、前方一致させる必要があります。設定したリダイレクト先はhttp://localhost:9000/zoomauth/check
としていたので、その前方を含める様にhttp://localhost:9000/
に設定しておきます。
「Information」にて「Optional」と書かれている箇所以外を入力し記述します。
ここが結構重要です。「Scopes」という箇所ではアプリの操作権限、アクセス範囲を設定できます。ユーザー情報の取得やミーティングの作成にもそれぞれスコープが用意されて、スコープ外の操作へのアクセスは401と認証エラーとなります。「Add Scopes」でスコープを追加します。
よくわからなければ全部追加してもいいですが、「Meeting」「User」のスコープを全て追加しておけば今回のアプリの実装は可能です。
最後に「Activation」にて確認します。不足箇所は以下の様にオレンジ文字で指摘されるので直しましょう。全てが入力できていれば後は問題ありません。「Install」などは押さなくても問題ありません。
それではLaravelを立ち上げましょう。インストールはされており、ユーザーテーブルのマイグレーションを行う前だと仮定します。
Larvelのプロジェクトルートに.env
という環境変数を記述するファイルがありますので、そちらに 「App Credentials」 で取得できるClient ID
とClient Secret
を設定します。
APP_NAME=Laravel
APP_ENV=local
...
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
ZOOM_CLIENT_ID=clientid
ZOOM_CLIENT_SECRET=clientsecret
.env
を更新したらキャッシュをクリアして反映させます。
php artisan config:clear
php artisan cache:clear
.env
に記述することで他のソースコード内でenv('ZOOM_CLIENT_ID')
と行った形で出力できます。また.env
は基本的に.gitignore
に登録されているので公開リポジトリに秘密鍵が載ってしまうという様な事故を防げます。
今回のアプリの管理者は一人ですが、もし複数人に使用してもらいたい時に「ユーザーごとにトークンを分けたいな」と思ったのでその改造をします。初期で用意されているユーザーテーブルを以下の様に書き換えます。
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
//ここから追加
$table->longText('zoom_code')->nullable()->default(null);
$table->longText('access_token')->nullable()->default(null);
$table->longText('refresh_token')->nullable()->default(null);
$table->timestamp('zoom_expires_in', 0)->nullable()->default(null);
$table->rememberToken();
$table->timestamps();
});
}
それぞれのカラムの説明はこの通り。
ユーザーごとのトークンが管理できる様になり、$user->auth()->access_token
みたいな感じでトークンを使用できます。
それではマイグレーションをしましょう。(仮ユーザーのseedも忘れずに)
php artisan migrate --seed
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (0.02 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (0.01 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (0.01 seconds)
Seeding: UsersTableSeeder
Seeded: UsersTableSeeder (0.06 seconds)
Database seeding completed successfully.
詳しくはアップしたリポジトリを見てください。スタイルはbootstrapで調整しています。ルート情報だけ載せておきます。
/ フォームを表示
/confirm フォームの受付完了確認画面
/admin 管理者用ページ
/login ログインページ
/logout ログアウトルート
/zoomoatuh/check zoom OAuthリダイレクト先
/form/alter ミーティングの時間変更画面
/form/delete ミーティングのキャンセル画面
それではLaravelとZoomの連携処理を実装していきます。連携処理はログインした管理者のアクセス配下で行います。管理画面は/admin
です。/admin
のコントローラーとビューは以下の通りです。
public function index(Request $request){
$user = auth()->user();
$noZoomCode = $user->zoom_code == null; //連携を行っているか
$zoomOuthLink = 'https://zoom.us/oauth/authorize?'.http_build_query([
'response_type'=>'code',
'redirect_uri'=>env('APP_URL').'/zoomoatuh/check',
'client_id'=>env('ZOOM_CLIENT_ID'),
]);
$oauthSuccess=false;
$meetings = Meeting::all();
return view('admin',compact('noZoomCode','zoomOuthLink','oauthSuccess','meetings'));
}
@extends('layouts.layout')
@section('main-content')
<div class="main-content">
@if($noZoomCode)
<div class="alert alert-danger mb-3" role="alert">
<h4 class="alert-heading">Zoomとの連携が行われていません。</h4>
<p>このシステムをご利用する場合、Zoomとの連携を行ってください。</p>
<a href="{{$zoomOuthLink}}" class="btn btn-danger">Zoomと連携</a>
</div>
@else
<h1>予約一覧</h1>
@endif
</div>
@endsection
管理画面では管理者がzoomと連携しているかで表示を変更しています。連携しているかはuser
テーブルのzoom_code
がnull
かで確認しています。
連携が済んでいない場合はzoomの連携確認画面へ飛ばすリンクボタンを表示させています。
この$zoomOuthLink
の作成はこちらのドキュメントにある通り、ルールがあります。
$zoomOuthLink = 'https://zoom.us/oauth/authorize?'.http_build_query([
'response_type'=>'code',
'redirect_uri'=>env('APP_URL').'/zoomoatuh/check',
'client_id'=>env('ZOOM_CLIENT_ID'),
]);
今はOAuthのステップの中で「ユーザー認証」というユーザーへ「このアプリ(Laravel)とzoomを連携してもいい?」とzoomが聞いている段階です。そのユーザー認証にはまずhttps://zoom.us/oauth/authorize
へGETで管理者本人がアクセスします。
その際にGETパラメータにresponse_type
、redirect_uri
、client_id
を入力します。
とある様にresponse_type
にはcode
という文字を設定します。そしてredirect_uri
はzoom アプリ作成時にも設定した通りのURLを入力しますので、http://localhost:9000/zoomauth/check
を設定。client_id
は.env
で設定値をenv()
で呼び出します。
それらをGETパラメータとして一つのURLにまとめます。以下の様な感じです。
https://zoom.us/oauth/authorize?response_type=code&redirect_uri=http://localhost:9000/zoomauth/check&client_id=clientid
予めサーバーサイドで作っておき、ボタンのリンクにはめ込んでおきます。画面では以下の様に表示されます。
ボタンをクリックすると以下の画面が表示されます。(正確には承認画面のGETを叩く)
管理者に対してこのアプリが自身のzoomアカウントに対して、何をするのかが書かれています。管理者はこの「認可」を押すと、redirect_uri
のリダイレクト先に飛ばされます。OAuthではこのリダイレクト先の処理が大切です!http://localhost:9000/zoomauth/check
のコントローラーは以下の通りです。(ビューはなし)
public function zoomOauth(Request $request){
$user = auth()->user();
if($user->zoom_code==null){
$code = $request['code'];
$user->zoom_code = $code;
$user->save();
$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));
$client = new \GuzzleHttp\Client([
'headers' => ['Authorization' => 'Basic '.$basic]
]);
$res = $client->request('POST','https://zoom.us/oauth/token',[
'query' => [
'grant_type'=>'authorization_code',
'code'=>$code,
'redirect_uri'=>'http://localhost:9000/zoomoatuh/check'
]
]);
$result = json_decode($res->getBody()->getContents());
$user->access_token= $result->access_token;
$user->refresh_token= $result->refresh_token;
$unixTime = time();
$user->zoom_expires_in= date("Y-m-d H:i:s",$unixTime+$result->expires_in);
$user->save();
return redirect()->route('amdin')->with([
'noZoomCode'=>false,
'oauthSuccess'=>true
]);
}
}
https://zoom.us/oauth/authorize
から http://localhost:9000/zoomauth/check
へリダイレクトされると自動的にGETパラメータに?code=~~~~
というものが付与されています。このcodeは後の認証に必要になります。
リダイレクトURLからcodeの値を取得します。いったんDBに保存してから、実際にAPIへリクエストするのに必要なアクセストークンの取得処理を行います。そこで以下の様なリクエストを行います。
$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));
$client = new \GuzzleHttp\Client([
'headers' => ['Authorization' => 'Basic '.$basic]
]);
$res = $client->request('POST','https://zoom.us/oauth/token',[
'query' => [
'grant_type'=>'authorization_code',
'code'=>$code,
'redirect_uri'=>'http://localhost:9000/zoomoatuh/check'
]
]);
Zoomにも書いてある通りの処理ですが、アクセストークンを得る https://zoom.us/oauth/token
というルートにアクセスするときは、まずリクエストヘッダーを付与します。リクエストヘッダーは 'headers' => ['Authorization' => 'Basic '.$basic]
です。ここに client IDとclient secretをコロンで付けて一つの文字列にし、それをbase64エンコードをします。つまり明示的に処理を表示すると以下の様な感じです。
client_id:cilent_secret //これで一行の文字列
↓
この値を64base encode
↓
si84nf7435934jdfsdfi... //エンコード化された文字。これを送る
そしてそれをリクエストヘッダーに付与します。'headers' => ['Authorization' => 'Basic '.$basic]
これを文字列として表示すると、Authorization: Basic si84nf7435934jdfsdfi…
みたいな感じです。ちなみに Basicとエンコード文字の間は半角が空いていますので注意。
リクエストヘッダーを付けたら先ほどと似た感じでGETパラメータを以下の様に設定します。
$res = $client->request('POST','https://zoom.us/oauth/token',[
'query' => [
'grant_type'=>'authorization_code',
'code'=>$code,
'redirect_uri'=>'http://localhost:9000/zoomoatuh/check'
]
])
grant_type
がauthorization_code
という文字とし、codeにはリダイレクト時についてきた値である$request['code']
を用います。redirect_uri
は先ほどと同じです。(redirect_uri
を別にすると認証が通りません!)
これでセットアップが完了です。実際のURLとしては以下の感じです。
https://zoom.us/oauth/token?grant_type=authorization_code&code=~~~~~&redirect_uri=http://localhost:9000/zoomoatuh/check
(そして直接は見えないですが、リクエストヘッダーには 「Authorization: Basic si84nf7435934jdfsdfi…」 という値がついています!
リクエストが成功するとアクセストークン を含んだレスポンスがJSONで戻ってきます。それを展開してDBへ保存します。zoom_expires_in
は現在時刻と足し合わせて、期限切れ時刻を計算してから格納しています。
/*
$resultの中身の例
{
"access_token": "eyJhbGciOiJIUz...",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJI..",
"expires_in": 3599,
"scope": "user:read"
}
*/
$result = json_decode($res->getBody()->getContents());
$user->access_token= $result->access_token;
$user->refresh_token= $result->refresh_token;
$unixTime = time();
$user->zoom_expires_in= date("Y-m-d H:i:s",$unixTime+$result->expires_in);
$user->save();
return redirect()->route('amdin')->with([
'noZoomCode'=>false,
'oauthSuccess'=>true
]);
そして最後は管理画面へリダイレクトしてあげます。管理者からしてみるとzoomの画面で「許可」を押すと元のサイトに戻って、グルグルローディングしてるなーと思ったら管理画面に戻ってきた感覚となります。実際の画面ではzoom連携の警告がなくなり以下の様な感じになります。
これでOAuthは完了です。意外と簡単ですね。access_tokenは1時間で切れてしまうので、APIアクセスの際は期限切れでないかをチェック、そしてダメならtokenをリフレッシュする機能が必要となります。次はaccess_tokenのチェック方ら連携した人のユーザー情報を取得するとともに、リフレッシュ機能を実装します。
ミーティングを作成したりなどはユーザーIDが必要となります。他のAPIで使用するのでコントローラー内の共通メソッドとして分離しておきましょう。以下の様にします。
class ZoomApiController extends Controller
{
//
protected function me(){
$user = auth()->user();
$client = new \GuzzleHttp\Client([
'headers' => ['Authorization' => 'Bearer '.$user->access_token]
]);
$res = $client->request('GET','https://api.zoom.us/v2/users/me');
$result = json_decode($res->getBody()->getContents());
// dd($result);
return $result;
}
...
}
ユーザーテーブルにaccess_token
があるので$user = auth()->user();
で現在のログインユーザーを取り出して、$user->access_token
にて出力します。
access_token
があればAPIへのアクセスはリクエストヘッダーにトークンを入れるだけでアクセスできます。リクエストヘッダーは'headers' => ['Authorization' => 'Bearer '.$user->access_token]
です。さっきはBasicだったのが、Bearer(ベアラー)
になっていますのでタイポに注意。
dd($result)
を有効にして出力してみると
こんな感じのJSONが返ってきますので、適宜IDなどを使用します。
access tokenは1時間しか持たないのでもし期限切れになった際にはリフレッシュトークンを使用してトークンを更新します。ちなみにリフレッシュトークンの有効期限は15年です笑私の場合は以下の様に実装しました。
protected function checkRefresh(){
$user = auth()->user();
$token_expires = new \DateTime($user->zoom_expires_in);
$now = new \DateTime();
if($now >= $token_expires){
$basic = base64_encode(env('ZOOM_CLIENT_ID').':'.env('ZOOM_CLIENT_SECRET'));
$client = new \GuzzleHttp\Client([
'headers' => ['Authorization' => 'Basic '.$basic]
]);
$res = $client->request('POST','https://zoom.us/oauth/token',[
'query' => [
'grant_type'=>'refresh_token',
'refresh_token'=>$user->refresh_token
]
]);
$result = json_decode($res->getBody()->getContents());
$user->access_token= $result->access_token;
$user->refresh_token= $result->access_token;
$unixTime = time();
$user->zoom_expires_in= date("Y-m-d H:i:s",$unixTime+$result->expires_in);
$user->save();
return $user;
}
return $user;
}
APIリクエストごとにトークンをチェックできる様にしています。有効期限をテーブルに保存してあるのでそれを比較して、現在時刻が有効期限を過ぎていたらリフレッシュ処理を行う様します。そして戻ってきたトークンをテーブルで更新させ、ユーザーモデルをreturnします。
有効期限ないであればそのままユーザーモデルを返却するという感じです。
それではフォームから入力された値を元にミーティングを作れる様にしましょう。メール通知は機能してはいませんが、実装したコードはコメントアウトさせてますので、頑張れる人はメールも実装してみてください。
まずフォームから取得したミーティング情報を格納するテーブルを以下の様に定義して、マイグレーションを実施します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class CreateMeeting extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('meeting', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('company_name');
$table->string('email');
$table->longText('content');
$table->timestamp('start_at', 0)->default(DB::raw('CURRENT_TIMESTAMP'));
$table->longText('hash');
$table->boolean('is_canceled');
$table->longText('zoom_meeting_id');
$table->longText('zoom_join_url');
$table->longText('zoom_start_url');
$table->longText('zoom_password');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('meeting');
}
}
start_at
はお客さんが入力した希望zoom開催時間です。本来は管理側の都合を合わせた機能にすべきですが今回はプロトタイプなので割愛。フロントからはdatetime形式で来たものを受け取ります。
hash
は後でお客さんがミーティングをキャンセルしたり、時間を変更するときにアクセスするURLに付けるランダムな文字列です。一種のパスワードみたいなものです。後ほど使い方を解説します。
そしてzoomへCreate Meeting API を送信すると、ミーティングURLなどが返ってきますのでそれをzoom_join_url
とzoom_start_url
に入れておきます 。他にフォームの内容を格納する箇所を定義してマイグレーションします。
フォームの画面以下の様に実装しました。
フォームのビューがPOSTリクエストを受けたら以下のコントローラーが実行されます。
public function createMeeting(Request $request){
$validator = Validator::make($request->all(),[
'email'=>'required|email:rfc',
'yourname'=>'required',
'companyname'=>'required',
'startAt'=>'date|required',
'content'=>'required|max:1000',
]);
$error = $validator->getMessageBag()->toArray();
//バリデーションエラーがあれば元の画面へ
if ($validator->fails()) {
return view('form',compact('error'));
}
$user = $this->checkRefresh();
$user = auth()->user();
$zoom_user = $this->me();
$url = 'https://api.zoom.us/v2/users/'.$zoom_user->id.'/meetings';
$client = new \GuzzleHttp\Client([
'headers' => [
'Authorization' => 'Bearer '.$user->access_token,
'Content-Type'=>'application/json'
],
]);
$topic = $request->companyname.' '.$request->yourname.'様 ご相談';
$meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);
$res = $client->request('POST',$url,[
\GuzzleHttp\RequestOptions::JSON => [
'topic'=>$topic,
'type'=>2,
'start_time'=>$request->startAt,
'password'=>$meeting_password
]
]);
$result = json_decode($res->getBody()->getContents());
$meeting = new Meeting();
$meeting->name=$request->yourname;
$meeting->company_name=$request->companyname;
$meeting->email=$request->email;
$meeting->content=$request->content;
$start = new \DateTime($result->start_time);
$meeting->start_at=$start;
$meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);
$meeting->is_canceled=false;
$meeting->zoom_meeting_id=$result->id;
$meeting->zoom_join_url=$result->join_url;
$meeting->zoom_start_url=$result->start_url;
$meeting->zoom_password=$result->password;
$meeting->save();
$format = $start->format('Y年m月d日 H時i分');
// $meeting->start_at = $format;
// $mail = new ContactMail($meeting);
// Mail::to($request->email)->send($mail);
return redirect('/confirm')->with([
'form_id'=>$meeting->id,
'name'=>$request->yourname,
'companyname'=>$request->companyname,
'content'=>$request->content,
'start_time'=>$format
]);
}
長いですが、バリデーションからAPIのアクセスまで一通り行われています。
まずは最初の方では有効期限のチェックを行い、そしてミーティングを作成するユーザー情報を取得しています。
$user = $this->checkRefresh();
$user = auth()->user();
$zoom_user = $this->me();
$url = 'https://api.zoom.us/v2/users/'.$zoom_user->id.'/meetings';
$client = new \GuzzleHttp\Client([
'headers' => [
'Authorization' => 'Bearer '.$user->access_token,
'Content-Type'=>'application/json'
],
]);
ミーティングの作成を行うエンドポイントは https://api.zoom.us/v2/users/{zoom_user_id}/meetings
です。{zoom_user_id}
にはme
で取得したuser_id
(連携したzoomアカウントのuser id)を挿入します。そしてリクエストヘッダーを付けてひとまず、GuzzleHttpのインスタンスを作成します。
$topic = $request->companyname.' '.$request->yourname.'様 ご相談';
$meeting_password = substr(base_convert(bin2hex(openssl_random_pseudo_bytes(9)),16,36),0,9);
$res = $client->request('POST',$url,[
\GuzzleHttp\RequestOptions::JSON => [
'topic'=>$topic,
'type'=>2,
'start_time'=>$request->startAt,
'password'=>$meeting_password
]
]);
$result = json_decode($res->getBody()->getContents());
お客さんに入力してもらう様のパスワードを生成し、そして指定したエンドポイントへPOSTします。ここでPOSTパラメーター内にミーティング設定情報をJSONで記入します。どんな値が設定できるかはここで確認できます。
上手く想像できない方は以下のGUIで行うzoom画面を参考にするといいです
ここで入力できる値は全て、APIでも入力できますのでリファランスでフォーマットなど確認しながら自分なりの設定をしましょう。私の場合、まずトピックを「{会社名} {客名様} ご相談」
として必ず定義しており、そこは'topic'=>$topic,
と定義してます。
start_timeは開催日時であり、フォームで入力された値を入れています。passwordは念のため付与しています。そしてAPIをリクエストします。
$result = json_decode($res->getBody()->getContents());
$meeting = new Meeting();
$meeting->name=$request->yourname;
$meeting->company_name=$request->companyname;
$meeting->email=$request->email;
$meeting->content=$request->content;
$start = new \DateTime($result->start_time);
$meeting->start_at=$start;
$meeting->hash=substr(base_convert(bin2hex(openssl_random_pseudo_bytes(64)),16,36),0,64);
$meeting->is_canceled=false;
$meeting->zoom_meeting_id=$result->id;
$meeting->zoom_join_url=$result->join_url;
$meeting->zoom_start_url=$result->start_url;
$meeting->zoom_password=$result->password;
$meeting->save();
$format = $start->format('Y年m月d日 H時i分');
// $meeting->start_at = $format;
// $mail = new ContactMail($meeting);
// Mail::to($request->email)->send($mail);
return redirect('/confirm')->with([
'form_id'=>$meeting->id,
'name'=>$request->yourname,
'companyname'=>$request->companyname,
'content'=>$request->content,
'start_time'=>$format
]);
$resultにzoomからのレスポンスがあるので適宜Meetingモデルやメールに格納します。そして最後にユーザーは確認画面が表示されます。
では実験してみます。連携した管理者のzoomアカウントでミーティング一覧をみています。リクエスト前はこの様に何もありません。
そこでフォームにこの様に入力していきます。(今回は管理者自身が入力)
そして送信を押すとちょっとロードして、こちらの画面にリダイレクトされます。
そして先ほどのzoom一覧を見てみると、
JUNE様ですね。きちんとミーティングが作られています。(時間がずれているのはコンテナ側のタイムゾーンの設定をすっかり忘れていたからです。)そしてテーブルを見てみると
きちんと作られていました。zoom_join_urlの値にアクセスすると
管理者が直接言っているのでミーティングの開始となっていますが、きちんと有効なミーティングURLを取得し保存できています。本来であればメールでお客様にこのURLとパスワードをお知らせします。
また管理画面では
この様にして一覧で確認ができます。ちなみに「ミーティングを削除」などのボタンはhttp://localhost:9000/form/delete?hash=cgckkwc040okko..
へリンクされています。form/delete
でミーティングを削除する確認画面へ飛べます。そして照合のためにhash=cgckkwc040okko..
の値を用いています。(途中にあった$hashの値です。)
お客様が匿名であり、ユーザーセッションによる識別ができない時は、予測困難なハッシュ付きURLをお客様だけのメールに渡してミーティングの制御が可能です。
以上がアプリの実装の流れです。今回作成したOAuth認証zoom アプリはプライベートなので作った本人しか今は利用できませんが、しっかり実装して審査を受けることでマーケットプレイスに出店して自由に使用してもらうことが可能になります。
OAuthも意外と簡単でしたが、公式のAPIリクエストライブラリが出ているわけでないので本格的な開発の際には独自のzoom APIライブラリを作っておくといいかもしれません。zoomでできることはこれだけでないので、もっと色々調べてみようと思います。ひとまず今回のzoomアプリはここまでとします。一応リポジトリに公開してあるのでぜひクローンして遊んでみてください。
なんか、これぐらいの規模で特定のアプリでの利用であればJWTでも十分でした汗。JWT編もそのうちやろうと思います。