業務でzoom apiを使って要約を取得したのでポイントをまとめます。
実装したいこと
- 本日分のZoom AI Companion(要約)を取得して要約文をSlackに投げる。
そのために以下のことを実装しました。
- meeting uuidの取得
- meeting summaryの取得
- slackに投稿
ZoomOauth認証
基本的には以下のようにコードを書いてzoom app marketplaceでアプリを作成すれば認証できます。
GAS側のコード
- 実行を担うdoGet関数
var Properties = PropertiesService.getScriptProperties();
var Client_Id = Properties.getProperty('CLIENT_ID');
var Client_Secret = Properties.getProperty('CLIENT_SECRET');
/**
* Authorizes and makes a request to the Zoom API.
*/
function doGet() {
var service = getZoomService_();
Logger.log(service);
if (!service.hasAccess()) {
var authorizationUrl = service.getAuthorizationUrl();
return HtmlService.createHtmlOutput("Click <a href='" + authorizationUrl + "' target='_blank'>here</a> to authorize.");
} else {
// User is authorized
return HtmlService.createHtmlOutput("Authentication successful!");
}
}
- OAuth2認証のセットアップ
function getZoomService_() {
return OAuth2.createService('zoomreport')
.setAuthorizationBaseUrl('https://zoom.us/oauth/authorize')
.setTokenUrl('https://zoom.us/oauth/token')
.setClientId(Client_Id)
.setClientSecret(Client_Secret)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getScriptProperties())
.setRedirectUri('https://script.google.com/macros/d/{GASのスクリプトID}/usercallback')
.setScope('{必要なスコープを追加}') //複数ある場合は「,」でスペースを開けずに繋げる
.setTokenHeaders({
'Authorization': 'Basic ' +
Utilities.base64Encode(Client_Id + ':' + Client_Secret),
});
}
- callback関数
function authCallback(request) {
var service = getZoomService_();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Success! Access Token: ' + service.getAccessToken());
} else {
return HtmlService.createHtmlOutput('Denied.');
}
}
- リセット関数:スコープなどを変更した際にリセットして再認証する
function reset() {
getZoomService_().reset();
}
Zoom app 側の設定は、適切なscopeを追加すれば問題ないと思います。
以下の記事などを参考にしました。
認証の流れ
GASで認証コードを書く+サービスからoauthを追加する → デプロイ(WEBアプリ) → zoom appの設定をする → デプロイされたWEBアプリのURLを踏むと認証できます。
meeting UUIDの取得
UUIDとは、会議ごとに与えらえる一意のIDのこと。
例えば毎日行われるAという会議のUUIDを取得する場合、meetingIDは毎日同じですが、UUIDは異なります。
UUIDの例: aDYlohsHRtCd4ii1uC2+hA==
今回meetingIDとは別にUUIDを取得するのは、zoon apiで要約を取得する際にUUIDが必要となるからです。
UUIDを直接取得することも考えましたが、取得したいミーティングがscheduledされていないものだったのでダメでした。(https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/meetings)
そのため、meetingID → UUID へと変換する必要があります。
(meetingIDの習得は省略します)
そのためのメソッドは、Get Past Meeting Details です。
meetingIDまたはUUIDをパラメータとしています。
meetingId: The meeting's ID or universally unique ID (UUID)
If you provide a meeting ID, the API will return a response for the latest meeting instance.
If you provide a meeting UUID that begins with a / character or contains the // characters, you must double encode the meeting UUID before making an API request.
コードは、以下のようになります。
function getMeetingUUID(meetingId) {
try {
const meetingDetails = sendZoomApiRequest(`/past_meetings/${meetingId}`);
return meetingDetails.uuid;
} catch (error) {
console.error('Error fetching meeting UUID:', error);
throw error;
}
}
ここで、apiの取得について理解が浅かったのでまとめてみました。
APIリクエストについて
この記事を参考にまとめます。
UrlFetchApp.fetch(URL[, パラメータ]) という構文を用います。
fetch: urlに対してパラメータparamsを渡してHTTPリクエストを行う。
私のコードでは、
const url = `${ZOOM_API_BASE_URL}${endpoint}`;
const options = {
method: method,
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
};
const response = UrlFetchApp.fetch(url, options);
としています。
optionsでは、method(GET)とheaders(リクエストのHTTPヘッダー)を指定してます。
その後、文字型にするために以下の変換を行います。
JSON.parse(response.getContentText());
これにより、文字列になります。
以下は、zoom apiにリクエストする機能を汎化した関数です。
const ZOOM_API_BASE_URL = 'https://api.zoom.us/v2';
function sendZoomApiRequest(endpoint, method = 'GET') {
const accessToken = getZoomAccessToken();
const url = `${ZOOM_API_BASE_URL}${endpoint}`;
const options = {
method: method,
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
};
try {
const response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() === 200) {
console.log(JSON.parse(response.getContentText()));
return JSON.parse(response.getContentText());
} else {
throw new Error(`API request failed with status ${response.getResponseCode()}: ${response.getContentText()}`);
}
} catch (error) {
console.error(`Error in Zoom API request to ${endpoint}:`, error);
throw error;
}
}
ここまでで、UUIDが取得できました。
残りは、UUIDからサマリーを取得してみます。
meeting summaryの取得
ドキュメントを参考に実装します。
apiリクエストをすると以下のような形式でデータが返ってきます。
{
"meeting_host_id": "30R7kT7bTIKSNUFEuH_Qlg",
"meeting_host_email": "jchill@example.com",
"meeting_uuid": "aDYlohsHRtCd4ii1uC2+hA==",
"meeting_id": 97763643886,
"meeting_topic": "My Meeting",
"meeting_start_time": "2019-07-15T23:24:52Z",
"meeting_end_time": "2020-07-15T23:30:19Z",
"summary_start_time": "2019-07-15T23:24:52Z",
"summary_end_time": "2020-07-15T23:30:19Z",
"summary_created_time": "2019-07-15T23:24:52Z",
"summary_last_modified_time": "2020-07-15T23:30:19Z",
"summary_title": "Meeting summary for my meeting",
"summary_overview": "Meeting overview",
"summary_details": [
{
"label": "Meeting overview",
"summary": "Meeting overview"
}
],
"next_steps": [
"step1"
],
"edited_summary": {
"summary_details": "Meeting overview",
"next_steps": [
"step1"
]
}
}
今回必要な情報は、summary_overview(概要)・summary_details(詳細)・next_steps(今後の方針など)です。
以下がコードです。基本的には、UUIDを取得したのと同じ要領で取得すれば行けます。
AI Companion機能がオンになっていることが必要です
function getSummaryFromZoom(uuid) {
let summaries = {};
try {
const encodedUUID = encodeUUID(uuid);
const summary = sendZoomApiRequest(`/meetings/${encodedUUID}/meeting_summary`);
// 全体のレスポンスを確認するため、ログ出力
//console.log('Summary response:', summary);
summaries = {
'overview': summary.summary_overview,
'details': summary.summary_details,
'nextstep': summary.next_steps
};
console.log(summaries.details);
return summaries;
} catch (error) {
console.error('Error fetching meeting summary:', error);
throw error;
}
}
また、/で始まる場合と//が含まれる場合二重エンコードする必要があるので以下の関数を作成しました。
// UUIDを必要に応じて二重エンコードする関数
function encodeUUID(uuid) {
if (uuid.startsWith('/') || uuid.includes('//')) {
return encodeURIComponent(encodeURIComponent(uuid));
}
return encodeURIComponent(uuid);
}
ここまでで、主要な関数を作成し終わりました。
slackに投稿する機能は、以下の記事が参考になりました。
肝となるのは、APIリクエストの部分でした。APIリクエストについての理解が浅いので今後勉強していきたいです。