Site icon GRIP.News

Mattermost에서 Google Calendar 일정 받기 (Serverless)

Google Calendar 의 일정 정보(생성, 삭제, 시작)를 다른 메신저나 서비스로 전달하는 방법은 매우 다양하지만, 결과를 보여주고자 하는 환경에서 별도의 인터페이스를 제공하지 않는 경우 중간에 릴레이 역할을 하는 매개체(서버)가 반드시 필요하다.  그런 의미에서 자체적으로 연결을 지원하는 Jandi 나 외부 플러그인을 사용하는 Slack 은 매우 편리하다.

현재 업무에 사용하는 메신저는 Slack-Alternative 인 Mattermost 로, Jandi 나 Slack 처럼 쉬운 연결을 위한 UI 를 제공하고 있지 않다. Server-Less 구조로 Google Calendar 의 일정 정보를 받을 수 있는 방법을 만들어보자.

 

STEP1. Mattermost Webhook URL 생성

Google Calendar 에서 발생한 Event 정보를 수신할 수 있는 웹훅 URL 을 생성해야한다. 햄버거 메뉴를 클릭하고, 통합기능에 위치한 수신 웹훅 메뉴를 클릭하자.

이 작업은 최고 관리자가 웹훅 기능을 활성화 한 경우에만 사용할 수 있다.

 

제목과 설명은 웹훅 설정 화면에 보이는 내용이기 때문에 간단히 입력하면 된다. 중요한건.

  1. 웹훅 메시지를 받을 ‘채널’ 을 정확하게 선택하고 ‘이 채널로 고정’ 을 선택할 것.
  2. 사용자 ID 는 화면에 노출되는 (웹훅 메시지를 전달하는) ID 로 실제 존재하는 가 아니어도 무관하다.
  3. 프로필 이미지를 보여주는 부분. 구글링을 통해 적당한 png 파일 URL 을 넣자.

 

WebHook URL 이 생성됐다. 이 URL 은 외부로 유출되어선 위험하다. 본인 확인을 위한 별도의 토큰 정보가 없기 때문에 URL 만 알아도 내가 지정한 채널에 메시지를 보낼 수 있기 때문.

 

STEP2. Google Drive, Spreadsheet Script 편집기 열기

Google Calendar 에서 발행하는 이벤트 정보를 Spreadsheet 의 Script 에서 받아 앞서 생성한 WebHook URL 로 보내는 작업을 만든다. 정확히 이 Script 은 GAS(Google Apps Script)라 부리며, Excel 의 VBS(Visual Basic Script)과 개념은 비슷하다. 물론 구문은 Javascript 이기 때문에 이질감 없는 코딩이 가능하다는게 장점.

새로운 Google Spreadsheet 를 생성하자. 그리고 도구 메뉴의 <> 스크립트 편집기를 클릭하자.

 

편집기가 열렸다.

 

STEP3. Google Calendar, Calendar ID 얻기

정보를 갖고올 Calendar ID 를 알아야한다. 사용 할 캘린더를 선택해 ‘설정 및 공유 > 캘린더 통합’ 메뉴의 캘린더 ID 를 복사하자.

 

STEP4. GAS 코딩

Google Calendar 를 위한 GAS 관련 내용은 https://developers.google.com/apps-script/reference/calendar/calendar-app 를 참고하자. 내가 구현하고자 한 기능은 다음 세가지다.

  1. 이벤트 15분 전 알려줄 것
  2. 새로 생성되었거나 수정된 이벤트가 있으면 알려줄 것
  3. 매일 오전 8시 그날 전체 일정을 요약해서 알려줄 것
var calendarId = "{캘린더ID}";
var webhookUrl = "{웹훅URL}";

function myFunction() {
  Logger.log("\n Event Notification Begin \n");
  
  begin15minAfterEvents(calendarId); // 이벤트 시작전
  listupNewEvents(calendarId); // 31일 이내
  // 매일 오전 8시에 그날 전체 일정을 요약해서 보낸다.

  Logger.log("\n Finished! \n");

}


function listupNewEvents(cal_id){ // 한달치 신규/수정된 일정을 검사한다.
  var events, msgBody, lastUpdatedStamp, currentDateStamp;
  var checkTermMonth = 31, checkTermMin = 15; // Condition Init.
  
  var cal = CalendarApp.getCalendarById(cal_id);
  var currentDate = new Date();
  var modifiedDate = new Date();
  
  for(var i=0; i<checkTermMonth; i++){ // 31일치 데이터를 갖고온다 (0=오늘)
    modifiedDate.setDate(modifiedDate.getDate()+i);
    events = cal.getEventsForDay(modifiedDate); // 그 날짜의 전체이벤트
    
    for(var k=0; k<events.length; k++){
      lastUpdatedStamp = parseInt(Utilities.formatDate(events[k].getLastUpdated(),"GMT+0900","YYYYMMddHHmm"));
      currentDateStamp = parseInt(Utilities.formatDate(currentDate,"GMT+0900","YYYYMMddHHmm"));
      
      if(currentDateStamp-lastUpdatedStamp < checkTermMin){ // 최종수정일 비교
       
        Logger.log(currentDateStamp+":"+lastUpdatedStamp+"\n");
        msgBody = "##### "+ events[k].getTitle()+"\n";

        if (events[k].isAllDayEvent()) { // 전일
          msgBody += "- "+Utilities.formatDate(events[k].getStartTime(),"GMT+0900","YYYY년 MM월 dd일")+"\n";
        } else {
          msgBody += "- "+Utilities.formatDate(events[k].getStartTime(),"GMT+0900","YYYY년 MM월 dd일 HH시 mm분")+" ~ "+Utilities.formatDate(events[k].getEndTime(),"GMT+0900","MM월 dd일 HH시 mm분")+"\n";          
        }

        msgBody += "- "+((events[k].getDescription())?events[k].getDescription():"내용 없음")+"\n\n";
        msgBody += "일정이 생성(수정) 되었습니다."
        
        // 새로운 일정은 건단위로 발송한다.
        postMmost(msgBody);
        Logger.log(msgBody);       
      }
    }
  }
}

function begin15minAfterEvents(cal_id){ // 15분뒤 시작되는 일정
  var currentDate = new Date(); 
  var cal = CalendarApp.getCalendarById(cal_id); 
  var events = cal.getEventsForDay(currentDate);
 
  
  for(var i=0; i < events.length; i++){
    if(!events[i].isAllDayEvent() && Utilities.formatDate(events[i].getStartTime(),"GMT+0900","YYYMMddHHmm")-Utilities.formatDate(currentDate,"GMT+0900","YYYMMddHHmm") == 15) { // 종일제외. 15분정각
        msgBody = "##### "+ events[i].getTitle()+"\n";
        msgBody += "- "+Utilities.formatDate(events[i].getStartTime(),"GMT+0900","YYYY년 MM월 dd일 HH시 mm분")+" ~ "+Utilities.formatDate(events[i].getEndTime(),"GMT+0900","MM월 dd일 HH시 mm분")+"\n";
        msgBody += "- "+((events[i].getDescription())?events[i].getDescription():"내용 없음")+"\n\n";
        msgBody += "###### 일정이 15분 후 시작 됩니다!";
        postMmost(msgBody);
        Logger.log(msgBody);
    }
  }
}

function postMmost(msgBody){

  var payload = {
    "text" : msgBody,
    "icon_url" : "https://img.icons8.com/color/420/google-calendar.png",
    "username" : "MTWORKS",
  }

  var options = {
    "method" : "POST",
    "contentType" : "application/json",
    "payload" : JSON.stringify(payload)
  }

  var response = UrlFetchApp.fetch(webhookUrl, options);
  var content = response.getContentText("UTF-8");

}

위 코드에서 매일 아침 8시에 그날 일정을 알려주는 부분은 만들지 않았지만 개발 방법은 간단하니 응용을.  위 코드가 정상적으로 동작하는지 확인하려면 메인 함수를 선택해 실행하면 된다.

내가 지정한 채널에 정상적으로 메시지가 온다면 성공적!

 

STEP5. 트리거 등록

Google Spreadsheet 의 수정 > 현재프로젝트의 트리거 메뉴를 선택하면 트리거를 생성할 수 있다. 난 15분 주기, 매일 오전 8시에 체크하도록 설정했다.

 

 

개발 자체는 어렵지 않다. 자, 마지막 Trigger 조건을 보면 이벤트 발생 시점을 캘린더로 지정할 수 있다. 이경우 새로운 이벤트가 발생하면 바로바로 알림을 줄 수 있게 되지만 왜 이걸 사용하지 않았냐고? 사실 이 스크립트는 사내 범용적으로 사용할 스크립트로 개발한 것으로 배포해 비 개발자도 사용하려고 했기 때문이다. 즉, 메뉴 설명 요소를 최소화 하기 위함에서 작업한 것이기 때문에.

만약 개발자 혹은 본인 전용 목적이라면 적절한 트리거를 응용해 호출 요소를 크게 줄일 수 있을 것이다.

Exit mobile version