@Satoh_D no blog

大分にUターンしたので記念に。調べたこととか作ったこととか食べたこととか

Zoom APIをDjangoで扱ってみる

Zoom APIを利用して部屋の作成とかする案件がありそうなので勉強のために調べてみたことメモ。
Djangoなのはこれも勉強のため。。

前提

ゴール

  • DjangoからZoomのOAuth認証ができること
  • DjangoからZoomの部屋を作成できること(予約情報の変更もできること)

手順

1. Zoomにてアプリ登録を行う

  • OAuth - Build an App - Documentationにアクセス
  • 右上「Create App」をクリック
    • Zoomアカウントでのログインを求められるのでログインする
  • 「Choose your app type」にて「OAuth > Create」をクリック
  • 「Create on OAuth app」というモーダルが表示されるので必要に応じて情報を入力する
    • App Name: 任意(今回はSamplaApp)
    • Choose app type: 任意(今回はAccount-level appを選択)
    • Would you like to publish this app on Zoom App Marketplace?: Offを選択
  • モーダル内「Create」をクリックするとアプリが作成され、以下情報が取得できる
    • 取得できる情報
      • Client ID
      • Client Secret
    • またOAuthでの認証後にリダイレクトされるページのURLを指定する(今回はhttp://localhost:8000/zoom/auth/completeとする)
    • Whitelist URLも登録しておく(今回はhttp://localhost:8000
  • 画面左側メニュー「Scopes」をクリック
  • 「Add scopes+」をクリック
    • 必要に応じて認証のスコープを設定する(今回はMeetingを作成できればよいのでuser:read:admin, meeting:write:admin, meeting:writeを選択)
      • APIのリファレンス(API Reference)に必要なスコープが書かれているので参考にする
    • スコープの選択が完了したらモーダル内「Done」をクリック

2. Djangoで実装を行う

2-1. デモアプリを作成する

アプリ名はわかりやすくzoomとする

$ python manage.py startapp zoom

settings.pyにてzoomを有効化する

# <project-root>/<project-app>/settings.py
INSTALLED_APPS = [
    ...,
    # My Applications
    'zoom.apps.ZoomConfig',
]

2-1. 認証, アクセストークンの取得

認証に関するルーティングを設定する

# <project-root>/<zoom/urls.py
from django.urls import path

from . import views

urlpatterns = [
    # /auth: 認証ページにリダイレクト
    # /auth/complete: 認証完了
    # /meeting: ミーティング一覧
    # /meeting/add: ミーティング作成
    path('', views.index, name='zoom_index'),
    path('auth/', views.auth, name='zoom_auth'),
    path('auth/complete', views.index, name='zoom_auth_complete'),
]

# <project-root>/<project-app>/urls.py
...
urlpattenrs = [
    ...,
    path('zoom/', include('zoom.urls')),
    ...,
]

ルーティングに対応するTemplate, Viewを作成する
とりあえず必要な箇所以外は一旦処理を省略する

<!-- <project-root>/zoom/templates/auth/auth.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Zoom - Auth</title>
</head>
<body>
    
    <h1>Zoom Auth</h1>

    <a href="{{ auth_href }}">Zoomで認証する</a>
</body>
</html>

<!-- <project-root>/zoom/templates/auth/complete.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Authentication complete</title>
</head>
<body>
    Zoom Authentication Complete
</body>
</html>
# <project-root>/zoom/views.py
import requests, base64, json

from django.shortcuts import render, redirect
from django.urls import reverse

# Create your views here.
def auth(request):
    '''認証ページへリダイレクトさせる'''

    client_id = 'SnSyF4YFQOGpJlXXTbIE4w'
    client_secret = 'ofqwMjnKuuQba4tmmTQVOlfgmj447uHw'

    if 'code' not in request.GET:
        print('get code')
        auth_url = 'https://zoom.us/oauth/authorize'
        response_type = 'code'
        # ngrokでlocalhostをSSL形式でアクセスできるようにする
        redirect_uri = 'https://XXXXXX.ngrok.io/zoom/auth'

        auth_href = auth_url + '?response_type=' + response_type + '&client_id=' + client_id + '&redirect_uri=' + redirect_uri
        
        return render(request, 'auth/auth.html', {
            'auth_href': auth_href
        })
    else:
        print('get token')
        auth_url = 'https://zoom.us/oauth/token'
        code = request.GET.get('code')
        grant_type = 'authorization_code'
        redirect_uri = 'https://XXXXXX.ngrok.io/zoom/auth'

        # basic認証用のコードを作成(client_ID:Client_Secretをbase64エンコード)
        client_basic = base64.b64encode('{0}:{1}'.format(client_id, client_secret).encode())

        # POST用のパラメータとカスタムヘッダを作成する
        post_payload = {
            'code': code,
            'grant_type': grant_type,
            'redirect_uri': redirect_uri
        }
        post_header = {
            'Authorization': 'Basic {0}'.format(client_basic.decode())
        }

        # # Exec POST
        response = requests.post(auth_url, data=post_payload, headers=post_header)
        response_text = json.loads(response.text)

        if 'access_token' in response_text:
            # 認証結果をセッションに保存
            request.session['zoom_access_token'] = response_text['access_token']
            request.session['zoom_token_type'] = response_text['token_type']
            request.session['zoom_refresh_token'] = response_text['refresh_token']
            request.session['zoom_expires_in'] = response_text['expires_in']
            request.session['zoom_scope'] = response_text['scope']

            return redirect('zoom_auth_complete')
        else:
            return render(request, 'auth/auth.html', {
                'auth_href': 'hoge'
            })

def auth_complete(request):
    '''認証完了ページ'''
    pass

ビルトインサーバを起動し、https://XXXXXX.ngrok.io/zoom/auth/にアクセスする リンクをクリックするとZoomのOAuth画面が表示される
※ Zoom APIを利用する場合はhttpsが必須らしくlocalhostを利用する場合はngrokなどのサービスを使ってhttps環境を作成する必要がある

OAuthを許可したら再度https://XXXXXX.ngrok.io/zoom/auth/にリダイレクトされればOk
この時、URLパラメータにcode=XXXXXXXXXXXというものがついている。これはアクセストークンの取得に必要となる

取得したコードをもとにアクセストークンを取得する
アクセストークンの取得にはZoomにアプリ登録した際のClientID,Client_Secretを使いBasic認証経由でPOSTする必要がある
Basic認証状はCientID:Client__Secretbase64エンコードして送信する

またPOSTの際に設定するredirect_uriパラメータだが、ここはZoomのアプリ登録時に設定したRedirect URL for OAuthの文字列を設定していたがそれではエラー(redirect uri mismatch)が出る
どうやらPOSTするページのURLでないとダメらしいので注意

# basic認証用のコードを作成(client_ID:Client_Secretをbase64エンコード)
client_basic = base64.b64encode('{0}:{1}'.format(client_id, client_secret).encode())

# POST用のパラメータとカスタムヘッダを作成する
post_payload = {
    'code': code,
    'grant_type': grant_type,
    'redirect_uri': redirect_uri
}
post_header = {
    # base64エンコードした状態だとbyte形式となるので.decode()でString形式に変換する
    'Authorization': 'Basic {0}'.format(client_basic.decode())
}

アクセストークンが取得できたらセッションに情報を追加して完了ページへリダイレクトする
簡略化のためにセッションにしているが本来はcookieあたりがいいと思われる

2-2. 部屋の作成(時間の変更)

アクセストークンが取得できたら試しにミーティング部屋を作ってみる
Viewsを以下の通り編集する

# <project-root>/zoom/views.py
def auth_complete(request):
    '''認証完了ページ'''

    get_user_url = 'https://api.zoom.us/v2/users'
    access_token = request.session['zoom_access_token']

    # ユーザー情報の取得
    get_user_headers = {
        'Authorization': 'Bearer {0}'.format(access_token)
    }
    get_user_response = requests.get(get_user_url, headers=get_user_headers)
    get_user_response_text = json.loads(get_user_response.text)
    user_info = get_user_response_text['users'][0]

    # 部屋の作成
    create_meeting_url = 'https://api.zoom.us/v2/users/{0}/meetings'.format(user_info['id'])
    create_meeting_params = {
        'topic': 'Sample Meeting',
        'type': 2, # scheduled meeting
        'start_time': '2020-11-02 T 12:00:00',
        'duration': 180,
        'timezone': user_info['timezone'],
    }
    create_meeting_params_json = json.dumps(create_meeting_params).encode('utf-8')
    create_meeting_headers = {
        'Authorization': 'Bearer {0}'.format(access_token),
        'Content-Type': 'application/json'
    }
    create_meeting_response = requests.post(create_meeting_url, data=create_meeting_params_json.decode(), headers=create_meeting_headers)
    create_meeting_response_text = json.loads(create_meeting_response.text)
    
    return render(request, 'auth/complete.html')

ミーティング部屋を作成するためにはユーザーIDが必要となるため、API経由で取得する
APIの詳細はList Users - Users - Zoom API - API Referenceを参照
アクセストークンはAuthrization: Bearer XXXXというヘッダをつけて送信する

# ユーザー情報の取得
get_user_headers = {
    'Authorization': 'Bearer {0}'.format(access_token)
}
get_user_response = requests.get(get_user_url, headers=get_user_headers)
get_user_response_text = json.loads(get_user_response.text)
user_info = get_user_response_text['users'][0]

ユーザーIDが取得できたらミーティング部屋を作成する
基本的に「ミーティング名称」「ミーティングタイプ」「開始日」「ミーティング時間」「タイムゾーン」あたりを設定していればよい
他にも様々なパラメータを渡すことができるので詳細は Create a Meeting - Meetings - Zoom API - API Reference を参照
この時、パラメータはdictではなくjsonを利用して渡すことになるため、ヘッダに「Content-Type: application/json」が必要となる(ない場合は300 - Unsupported Content Typeというエラーが返ってくる)

# 部屋の作成
create_meeting_url = 'https://api.zoom.us/v2/users/{0}/meetings'.format(user_info['id'])
create_meeting_params = {
    'topic': 'Sample Meeting',
    'type': 2, # scheduled meeting
    'start_time': '2020-11-02 T 12:00:00',
    'duration': 180,
    'timezone': user_info['timezone'],
}
create_meeting_params_json = json.dumps(create_meeting_params).encode('utf-8')
create_meeting_headers = {
    'Authorization': 'Bearer {0}'.format(access_token),
    'Content-Type': 'application/json'
}
create_meeting_response = requests.post(create_meeting_url, data=create_meeting_params_json.decode(), headers=create_meeting_headers)
create_meeting_response_text = json.loads(create_meeting_response.text)

リクエストが正常に完了したらZoomを立ち上げ、スケジュールに追加されていれば処理完了

参考サイト