구글 캘린더에 자연어로 이벤트 추가하는 자바스크립트 애플리케이션 만들기

구글캘린더에 자연어로 이벤트를 추가하는 아주 간단한 웹 애플리케이션을 만들었다.

‘굳이’ 서버사이드 스크립트를 전혀 사용하지 않고 만들었는데, 첫째는 서버에 부담을 주기 싫었고 둘째는 웹서버가 없어도 로컬에서 웹브라우저만으로 동작할 수 있게 하고 싶었기 때문이다.

여기에서 테스트해볼 수 있다. 소스코드도 거기서 참조하고 있는 자바스크립트 파일들이 전부이다.

만들기

OAuth 인증

요게 가장 난감하다. OAuth 인증과정에서 구글 서버하고 내 서버가 서로 통신을 해야 하기 때문이다. 이걸 순수하게 클라이언트 사이드 스크립트로만 처리하려면 약간 지저분해지는 것은 피하기 어렵다.

우선 여기서 웹애플리케이션용 클라이언트 아이디를 받급받자.

client id를 얻었다면 이걸로 구글 서버에 access token을 달라고 요청하는 코드를 작성한다. redirect_uri 를 줘야 하는데, 이건 window.location에서 얻어온다. 요청하는 것도 window.location을 이용한다.

var redirect_uri =
    'https://' + window.location.host + window.location.pathname;

window.location = "https://accounts.google.com/o/oauth2/auth" +
    "?response_type=token" +
    "&client_id=" + client_id +
    "&redirect_uri=" + redirect_uri +
    "&scope=https://www.googleapis.com/auth/calendar" +
    "&state=profile";

인증이 되면 구글의 인증 서버가 redirect_uri로 요청을 보낸다. access token이 query에 붙어서 오므로 이것을 뜯어내어 사용한다. window.location.hash.substring(1) 로 가져올 수 있다.

var params = {};
var queryString = window.location.hash.substring(1);
var regex = /([^&=]+)=([^&]*)/g;
var m;

while (m = regex.exec(queryString)) {
    params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}

access_token = params.access_token;
window.location.hash = "";

주소창에 access token이 떡 노출된다. 찝찝하니까 window.location.hash = ""; 로 지워버렸다. 깔끔하지 못하지만 ‘굳이’ 서버를 거치지 않고 싶으니 별 수 없다.

위의 스크립트는 최우선으로 실행되도록 한다. 왜냐면 저 인증과정에서 구글의 인증 페이지로 이동하게 되므로 다른 작업은 해봤자 무의미한 시간 및 트래픽 낭비이기 때문이다.

그럼 여기서부터는 HTML 페이지 및 자바스크립트 파일들을 다 로딩한다음 작업을 수행해도 된다. jquery도 마음껏 쓸 수 있다.

access token은 사용하기 전에 반드시 validation을 해야 한다. 이제 jquery를 써서 비동기로 요청을 보내자. validation에 성공하면 callback 함수를 호출해서 다음 할 일을 진행하도록 하자.

var validateAccessToken = function(callback) {
  var url =
    'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' +
    access_token;

  $.get(url, function(data) {
    callback();
  }).error(function() {
    notifyError('access token is invalid');
  });
}

validation까지 끝났다면 모든 준비는 완료되었다. 이제 진짜 하고 싶은 일을 할 수 있다.

이벤트 입력받아 자연어 처리하기

사용자의 캘린더에 이벤트를 추가하려면, 우선 사용자로부터 이벤트를 입력받아야 한다.

내가 생각하기에 가장 쉬운 이벤트 추가 방법은 그냥 자연어로 입력받는 것이다. 이렇게:

party with you@mail.com at my house on Friday 7pm

제대로 된 자연어 처리 기능을 넣는 것은 너무 어려우니 아주 단순하게 처리해보자.

내가 택한 방식은, 일단 조사 단위로 자르고 각 구절을 파싱하는 것이다. ‘with’, ‘at’, ‘on’, ‘in’ 기준으로 자른다.

var phrases = [];
var prepositions = ['with', 'at', 'on', 'in'];

for (var i in prepositions) {
  var name = prepositions[i];
  var matched = source.match(new RegExp('\b' + name + '\b'));
  if (matched) {
    phrases.push({name: name, index: matched.index});
  }
}

이렇게 잘라내면, ‘party’, ‘you@mail.com’, ‘my house’, ‘Friday 7pm’을 얻을 수 있다.

그리고 파싱을 하면 되는데… 사실 파싱이라고 해봤자, 이벤트 요약(party), 참석자(you@mail.com), 장소(my house)는 굳이 파싱할 필요가 없다. 그대로 이벤트에 넣어주면 된다.

일시(Friday 7pm)만 파싱해주면 되는데, 이건 Sugar 라는 훌륭한 라이브러리가 있으므로 그걸 그대로 사용했다.

date = Date.create(phrase);

그럼 이제 필요한 건 다 얻었으니, 이벤트를 만들어서 사용자의 캘린더에 넣어주면 된다.

구글 캘린더 API로 이벤트 추가하기

우선 캘린더를 얻어야 한다. 사용자에게 캘린더가 여러개 있을 수도 있는데, 그냥 여기선 첫번째 캘린더를 얻자.

var getTheFirstCalendar = function(callback) {
  url = 'https://www.googleapis.com/calendar/v3/users/me/calendarList?maxResults=1&minAccessRole=writer';
  $.ajax(url,
    {
      datatype: 'json',
      headers: {
        Authorization: 'Bearer ' + access_token
      },
      success: function(data) {
        if (typeof data === 'string') {
          data = JSON.parse(data);
        }
        callback(data.items[0].id);
      },
      error: function(jqXHR, textStatus, error) {
        notifyError('Failed to get a calendar: ' + error + 'from ' + url);
      }
    }
  );
}

여기서도 마찬가지로 성공했으면 다음 작업 진행을 위해 callback을 호출하게 했다.

캘린더까지 얻어왔다면 이벤트를 추가하면 된다. 아까 사용자로부터 입력받아 파싱까지 완료된 이벤트 데이터를 구글 캘린더 API에서 요구하는 형식에 맞게 맞춰주고 json으로 만들어 이벤트 추가 요청을 보낸다.

var addEvent = function(calendar_id) {
  if (data.date) {
    data.end = data.start = {
      dateTime: moment(data.date).format()
    }
  } else {
    data.start = data.end = {
      dateTime: moment().format()
    };
  }

  if (data.emails) {
    data.attendees = [];
    for (var i in data.emails) {
      data.attendees.push({
        email: data.emails[i],
        responseStatus: 'needsAction'
      });
    };
  }

  data.reminders = { useDefault: true };

  calendar_id = encodeURIComponent(calendar_id);
  url = 'https://www.googleapis.com/calendar/v3/calendars/' + calendar_id + '/events?sendNotifications=false';
  $.ajax(url,
    {
      contentType: 'application/json; charset=utf8',
      datatype: 'json',
      type: 'post',
      headers: {
        Authorization: 'Bearer ' + access_token
      },
      data: JSON.stringify(data),
      success: function(data) {
        notifySuccess('Created successfully. Click to go to the event.');
      },
      error: function(jqXHR, textStatus, error) {
        notifyError('Failed to create an event: ' + error + ' from ' + url);
      }
    }
  );
}

폼 제출(submit)

form submit을 ‘굳이’ 자바스크립트로 했다. form으로 submit 한 요청에 대한 응답을 처리할 수 있는 이벤트가 있고 거기에 핸들러를 등록할 수 있다면 안 해도 될 것 같은데, 그런건 못 찾았다.

$('#new-event').submit(function() {
  $('#submit_button').button('loading');
  getTheFirstCalendar(addEvent);
  return false;
});

기타 고려사항

HTTPS

OAuth는 HTTP, HTTPS 어떤 scheme으로도 사용할 수 있지만 HTTP를 사용했다간 access token이 암호화되지 않은 채로 사용자에게 전달될테니 그보다는 HTTPS를 사용하자. startssl에서 무료로 인증서를 발급받을 수 있다.

Free (( 아이콘을 얻은 사이트에 Free라고 되어있었다. 공짜라고 하려다가 Free는 공짜가 아니야! 라고 할까봐 )) 아이콘

무료로 쓸 수 있는 품질 좋은 아이콘 없나 찾아 헤매다가 glyphish라는 아이콘 셋을 찾았다. 200개는 무료, 400개는 25달러다.

브라우저 호환성

몽땅 클라이언트 사이드 자바스크립트로 동작하므로, 브라우저 호환성은 완전 꽝이다. 지금 내가 만든 이것도 꽤 많은 브라우저에서 안 돌아갈 것이다.

그러니 꼭 클라이언트 사이드에서 전체 기능이 동작할 필요가 없다면 굳이 이렇게 만들지 않는 것이 좋다.

Advertisements

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중