Google Apps Script, Zoom API와 webhook을 활용해 출결 자동화 시트 만들기

EenSung Kim·2022년 5월 31일
0

"삽질의 과정을 기록해 보았습니다"


intro

Zoom 회의에 참석한 사람들의 명단과 참석 시간을 출석부처럼 한 눈에 볼 수 있게 정리해보자. 이 글은 이러한 동기로부터 시작된 여러 날의 삽질에 관한 기록입니다.

보시면서 왜 굳이 Apps Script 를 사용해야만 했는지, Zoom API 나 웹훅을 굳이 사용하지 않아도 되는 것은 아니었는지, 사실 이미 잘 만들어진 툴이 있었다든지 등등 여러 의문이 드셨다면 아마 그 의문이 크게 틀리지는 않을 겁니다. 아무튼 시작해보겠습니다.


과정

1. Apps Script 로 Zoom API 사용하기

Apps Script 는 자바스크립트 문법을 활용해 코드를 작성하고, 이를 통해 Google 의 여러 제품과 결합한 자동화를 가능하게 해주는 도구입니다. 이미 구글 시트를 활용해서 여러가지 작업을 하고 있었기 때문에, 출석부에 관한 고민이 시작되었을 때 Apps Script 를 자연스럽게 떠올리게 되었습니다.

Zoom API 를 사용하는 과정은 크게 어렵지는 않았습니다.(어렵지는 않았지만 시간은 좀 걸렸습니다..) Zoom 개발자 문서의 JWT 부분을 참고해서 앱을 등록하고, API Key 와 API Secret 를 가져와 JWT 토큰을 생성해 전달하면 되었으니까요. 인터넷 세상의 뛰어난 형님들께서 이미 코드도 짜두신 덕분에 저도 관련한 도움을 많이 받았습니다.

[JWT 토큰을 생성하는 코드]

// zoom api 활용을 위한 jwt 생성 함수
const getZoomAccessToken = (ZOOM_API_KEY, ZOOM_API_SECRET) => {
  const encode = (text) => Utilities.base64EncodeWebSafe(text).replace(/=+$/, '');
  const header = { alg: 'HS256', typ: 'JWT' };
  const encodedHeader = encode(JSON.stringify(header));
  const payload = {
    iss: ZOOM_API_KEY,
    exp: Date.now() + 3600,
  };
  const encodedPayload = encode(JSON.stringify(payload));
  const toSign = `${encodedHeader}.${encodedPayload}`;
  const signature = encode(
    Utilities.computeHmacSha256Signature(toSign, ZOOM_API_SECRET)
  );
  return `${toSign}.${signature}`;
};

[참여자 정보를 가져오는 Zoom API 활용]

// 참여자 정보를 불러오기 위한 api call
function fetchParticipants(jwtToken, uuid) {
  const options = {
    method: 'GET',
    headers: {
      authorization: `Bearer ${jwtToken}`,
    },
  }
  let params = encodeURIComponent(encodeURIComponent(uuid));
  const response = UrlFetchApp.fetch(`https://api.zoom.us/v2/report/meetings/${params}/participants`, options);

  return JSON.parse(response).participants;
}

2. Webhook 연결하기

위의 내용과 제가 원하는 작업을 코드로 작성해 가장 기본적인 참가자 정보를 불러올 수 있었습니다. 문제는 함수를 매번 직접 실행을 해야한다는 점이었죠. 그러다가 웹훅의 존재에 대해 알게 되었습니다.

Zoom 에서는 여러가지 이벤트에 연결된 웹훅을 제공합니다. 저는 Meeting이 끝날 때 발생하는 웹훅 이벤트를 활용하기로 했습니다.

Apps Script 로 작성한 코드는 아주 손쉽게 웹앱으로 배포가 가능합니다. 배포 시 만들어지는 URL 을 Zoom 의 웹훅 이벤트에 연결하면 웹훅 이벤트가 발생할 때 연동된 Apps Script의 함수를 실행할 수 있습니다. Zoom 의 웹훅 이벤트가 HTTP POST 요청을 날려주면, 웹앱으로 배포한 Apps Script의 doPost(e) 함수가 실행되는 구조입니다.


3. 문제 해결

1) doPost(e) 함수가 중복 실행되는 현상

쌓이는 기록을 확인하다 보니, doPost(e) 함수가 중복으로 실행이 되는 것을 발견했습니다. Executions 내역에서도 확인이 되더라구요. 한참을 헤맨 끝에 발견한 이유는 다음과 같았습니다.

웹훅 이벤트가 발생해 줌이 HTTP 요청을 전달하게 되면, 이를 전달받은 URL에서는 3초 안에 200 또는 204 HTTP 상태 코드로 응답해야 합니다.

아래에서는 다음과 같은 추가적인 내용을 언급하고 있었죠. 만약 notification 이 성공으로 인식되지 않을 경우 Zoom 은 웹훅 이벤트 발생 5분 후, 25분 후, 85분 후에 다시 이벤트가 발생했음을 전달합니다.

doPost(e) 함수에다가 여러 기능을 다 넣어두다 보니 함수의 실행이 오래 걸리면서, 3초 안에 응답을 하지 않아서 발생한 문제였죠.

2) Zoom API 가 이상한 Participants 정보를 전달해주는 현상

웹훅 이벤트로 회의가 끝나자마자 API 요청을 보내다 보니, 간혹 응답되는 정보가 이상한 경우들이 있었습니다. 제가 활용했던 API 는 회의 참여자 리포트를 받아오는 API 였는데요. join_time 과 leave_time 이 특정한 경우에 똑같은 정보로 들어오는 현상이 발생한 것이죠.

아직도 원인은 모르겠지만, 이를 해결하기 위해서는 비동기 또는 딜레이를 주고 함수를 실행시켜야 했습니다.

3) 근데 비동기 정보를 못찾겠어요..

시간 딜레이를 주고 함수를 실행시킬 수 있지만, 그렇게 하면 1) 의 문제가 더욱 심화될 수 밖에 없습니다. 그래서 Apps Script 에서 비동기로 함수를 실행할 수 있는지를 살펴보았는데, 아쉽게도 제 짧은 노력으로는 비동기 실행에 관한 정보를 찾을 수 없었습니다.

4) 나만의 솔루션

그래서 저는 다음과 같은 해결방법을 선택했습니다. Zoom 회의가 끝날 때 참여자 정보가 아닌 회의의 정보를 받아와 시트에 저장해 두었다가, 하루에 1회 해당 회의 정보를 활용해 참여자 정보를 업데이트 하는 것이죠.

트리거를 활용하면 원하는 시간에 함수가 실행될 수 있도록 할 수 있기 때문에 저는 이 방식으로 문제를 해결했습니다.


그 외에

웹훅으로 발생한 이벤트의 로그가 찍히지 않는 관계로, 커스텀으로 로그를 생성하는 함수를 만들어 별도의 시트에 이를 기록하도록 만들기도 했습니다. (분명 로그를 확인할 수 있어야 할 것 같은데, 제가 뭐를 놓치고 있는건지 모르겠지만 말이죠)

function log(event, message){
  SpreadsheetApp.getActive().getSheetByName('executionLog').appendRow([new Date(), event, message])
}

outro

분명 더 나은 방법이 있겠지만, 일단 굴러갈 수 있도록 자동화를 진행했다는데 의의를 두고 있습니다. 새로운 것들을 익혀나가는 이 과정이 꽤나 즐거웠기 때문에 개인적으로는 꽤나 만족스럽습니다.

더 깔끔한 방법이 분명 어딘가에 있겠죠. 이런 경험들이 쌓이다 보면 더 나은 길을 보다 빠르게 찾아갈 수 있지 않을까 합니다.

profile
iOS 개발자로 전직하기 위해 공부 중입니다.

0개의 댓글