나는 어렵고 힘든 일이 있으면 게으른 사람에게 시킨다.
그들은 항상 쉽게 해결할 수 있는 방법을 찾아내기 때문이다.
-빌 게이츠-
슬랙 봇이란?
개발자들이 널리 사용하는 메신저 앱 Slack에서는 슬랙 봇이라는 녀석을 사용할 수 있다.
슬랙 봇은 챗봇의 일종이며, 다른 사람이 만들어놓은 슬랙 봇을 이용할 수도 있고, 스스로 직접 개발할 수도 있다.
본인은 하루 중 최대의 난제. 점심때 뭐 먹지? 를 슬랙 봇으로 해결하고 있다. (제가 만든 건 아닙니다.)
슬랙 봇을 만들게 된 계기
대학 후배들과 진행하는 개발 스터디에서 보다 효율적인 커뮤니케이션을 위해 메신저를 카카오톡에서 슬랙(Slack)으로 옮겼다.
후배들과 스터디를 할 때면 가끔 구글링으로 간단하게 해결 할 수 있는 질문들이 나오곤 하는데, 이럴 때마다 구글 검색 링크를 던져주곤 했다.
그러던 어느 날 '누가 검색 좀 대신해줬으면 좋겠네!'라는 생각이 들었다.
그래서 구글 검색을 대신 해주는 슬랙 봇을 만들기로 했다.
요구사항 정의
구체적으로 입력과 출력을 정의해보자.
내가 원하는 기능은 아주 심플하다.
슬랙 봇에게 검색 키워드를 던져주면, 해당 키워드로 검색을 한 링크를 던져주는 것이다.
입력과 출력의 예시는 아래와 같다.
이종호 : 구글 검색 "구글링 하는 법"
slack bot : '구글링 하는 법'에 대한 검색 결과입니다.
https://www.google.co.kr/search?q=구글링+하는+법&rlz=1C1GCEU_koKR821KR821&oq=구글링+하는+법&aqs=chrome..69i57.24405j0j1&sourceid=chrome&ie=UTF-8
물론 영어 검색도 되어야 한다.
이종호 : 구글 검색 "how to google search"
slack bot : 'how to google search'에 대한 검색 결과입니다.
https://www.google.co.kr/search?q=how+to+google+search&rlz=1C1GCEU_koKR821KR821&oq=how+to+google&aqs=chrome.1.69i57j0l5.8266j1j1&sourceid=chrome&ie=UTF-8
고려사항
대화 중 봇이 시도 때도 없이 튀어나오면 곤란하다.
그러므로 봇이 인식할 문구는 일반적인 채팅 문구 중 겹치지 않을 것으로 선택해야 한다.
대략적인 설계
구글에서 검색을 하면 URI과 함께 날아가는 쿼리 파라미터들을 볼 수 있다.
예를 들어 '구글링 하는 법'에 대한 검색시 URI는 아래와 같다.
쿼리 파라미터 별 값
- newwindow : 1
- rlz : 1C1GCEU_koKR821KR821
- ei : 0cgJXImjN5C90PEP0-eiuAk
- q : 구글링+하는+법
- oq : 구글링+하는+법
- gs_l : psy-ab.3…0.0..61998…0.0..0.0.0…….0……gws-wiz._S1iG4vAG9s
rlz
, ei
, gs_l
이 아이들은 무엇을 하는지 한눈에 봐서는 식별이 쉽지 않지만 q
, op
는 내가 입력한 키워드와 거의 동일한 형태를 띄고 있다. (공백만 +
문자로 치환되어 있음)
q
나 oq
중 하나만 입력해도 결과가 잘 나오는지 테스트를 해 보았다.
https://www.google.co.kr/search?q=구글링+하는+법
https://www.google.co.kr/search?q=how+to+search+google
테스트 결과 한글도 영어도 아주 잘 나오는 것을 확인할 수 있었다.
시나리오
테스트도 잘 성공했겠다. 간단하게 시나리오를 만들어보자.
- 검색어를 파라미터로 입력받음
- 검색어의 빈 공백을
+
문자열로 치환 - [
https://www.google.co.kr/search?q=](https://www.google.co.kr/search?q=){검색어}
형태로 문자열을 반환
이정도 시나리오로도 내가 원하는 역할은 충분히 수행 할 수 있을 것 같다.
슬랙 봇 구현하기
※ 저는 Node
에 익숙하지 않습니다.(실제 프로덕션을 Node
로 구현해 본 경험이 없습니다.)그렇지만 slack bot을 구현하고 배포하기에 Java
나 C#
, .NET
보다는 Node
가 적절하다고 판단해서 Node로 작성하였습니다. Node
스럽지(?) 않은 부분이 있더라도 이해를 바랍니다 :)
위의 시나리오대로 실제로 슬랙 봇을 구현해보자.
해당 봇은 TDD를 사용하여 구현하였다. 그렇지만 본 글에서는 TDD가 무엇인지는 따로 설명하지 않는다.
TDD가 무엇인지, 어떻게 하는지 궁금하신 분 들은 이 글을 참고하시면 도움이 될 듯하다.
원하는 결괏값에 대해 먼저 테스트 코드를 작성하였다.
const assert = require("assert");
const convertSearchQuery = require("../query").convertSearchQuery;
describe("기능 테스트", function () {
it("검색을 원하는 케이스 - 한글", function () {
assert.strictEqual(convertSearchQuery("검색! 구글링 하는 법"), "https://www.google.co.kr/search?q=구글링+하는+법");
});
it("검색을 원하는 케이스 - 영문", function () {
assert.strictEqual(convertSearchQuery("검색! how to search google"), "https://www.google.co.kr/search?q=how+to+search+google");
});
it("검색을 원하는 케이스 - 띄워쓰기가 안 되어 있는 경우 - 한글", function () {
assert.strictEqual(convertSearchQuery("검색!구글링 하는 법"), "https://www.google.co.kr/search?q=구글링+하는+법");
});
it("검색을 원하는 케이스 - 띄워쓰기가 안 되어 있는 경우 - 영문", function () {
assert.strictEqual(convertSearchQuery("검색!how to search google"), "https://www.google.co.kr/search?q=how+to+search+google");
});
});
그리고, 해당 테스트를 통과하도록 비즈니스 로직을 작성하였다.
/**
* 메세지를 구글 검색 쿼리로 변환하여 반환
*/
function convertSearchQuery(input) {
let query = extractSearchQuery(input);
return "https://www.google.co.kr/search?q=" + query;
}
/**
* 메세지에서 쿼리 파라미터를 추출함
*/
function extractSearchQuery(input) {
return input.split("검색!")[1] //쿼리 추출 대상은 검색! 문자열 이후 값
.replace(/(^\s*)|(\s*$)/gi, "") //띄워쓰기가 되어 있지 않을 수 있으므로 앞 뒤 공백 제거
.split(" ")
.join("+");
}
module.exports.convertSearchQuery = convertSearchQuery;
Node의 진입점
require('dotenv').config();
const {RTMClient} = require("@slack/client");
const { WebClient } = require('@slack/client');
const convertSearchQuery = require("./query").convertSearchQuery;
const token = process.env.SLACK_TOKEN;
if (!token) {
console.log('You must specify a token to use this example');
process.exitCode = 1;
return;
}
const web = new WebClient(token);
const rtm = new RTMClient(token);
rtm.start();
/**
* 메세지 입력 이벤트 핸들러
*/
rtm.on("message", (message) => {
if(!message.text.includes("검색!")){
return;
}
let queryURI = convertSearchQuery(message.text);
if(queryURI){
//bot icon/name을 표기하기 위하여 WebClient 사용
web.chat.postMessage({
channel : message.channel,
text : queryURI,
as_user : true
});
}
});
잠깐 RTMClient와 WebClient에 대해서 설명을 하고 넘어가도록 하자.
RTM, Web API 모두 Slack에서 제공해주는 API의 종류이다.
각 특징은 아래와 같다.
Web API
Slack에서 풍부(Rich)한 대화식(Interactive) 메시지를 보내는 주된 수단
HTTP 프로토콜을 사용한다.
'풍부'하다는 것은 아래의 사용 예시를 참고. RTM에 비해 수행할 수 있는 기능이 많다.
- 채널 및 DM의 메시지와 같은 이벤트 보내기
- 슬랙 채널 및 사용자 그룹 생성 및 수정
- 파일, 메시지 첨부 파일 및 반응 업로드 및 편집
RTM
RTM(Real Time Messaging, 실시간 메시징)API는 이벤트 수신 및 간단한 메시지 전송을 허용하는 웹 소켓을 사용함
RTM API는 연결된 모든 클라이언트에 이벤트의 스트림을 전송
앱이 여러 슬랙 팀에 설치되는 경우 Resouce Intensive 할 수 있음.
내 슬랙 봇의 경우 RTM의 특징이 필요가 없는 간단한 앱이기에 원래는 Web Client를 사용하는 것이 맞다.
그렇지만 처음에 RTM 방식으로 구현했다가, 나중에 Web Client로 넘어갔다 (정확히는, 넘어가는 중이다.)
왜? Web Client API의 존재를 알기 전에 RTM 예시를 보자마자 프로토타입을 만들어버렸기 때문이다. (..)
이렇게 길지 않은 코드로도 내가 원했던 슬랙 봇의 기능은 잘 동작했다.
전체 소스코드는 Github Repo에 공개할 예정이다.
이제 배포를 해 보도록 하자.
참고 자료 : [Which API is right for your Slack app?](Which API is right for your Slack app?)
heroku에 봇 배포하기
배포할 클라우드 서비스를 고민했을 때, 가장 많이 사용해본 AWS가 제일 먼저 생각났다.
그렇지만 간단한 slack bot 배포에는 heroku가 적합한 것 같아서 heroku로 선택!
아랫글에서 heroku의 서비스를 시작하고 봇을 배포하는 데까지 필요한 부분을 잘 설명하고 있다.
그래서 heroku와 배포에 대한 별도의 부가설명은 하지 않고 내 생각만 조금 보태면 아래와 같다.
- 토큰을 직접 코드에 넣는 방식보다는 환경 변수 파일 (.env)에 넣어서 따로 관리하는 방법을 추천한다.
- 예제는 RTM 으로 되어 있는데, RTM 방식이 맞을지, 아니면 Web API 방식이 맞을지 한 번 더 생각해보면 좋겠다. (나는 보자마자 바로 만들어버려서, 나중에 Web API로 다시 바꿔야 했다.)
heroku repo에 push만 하면 빌드와 배포까지 자동으로 되던데 참 편리해진 것 같다.
(혹은 요즘 클라우드 서비스는 이런 건 기본으로 지원해 주는데 클라우드 서비스를 안 쓴지 너무 오래돼서 내가 몰랐거나. ㅠ)
서비스 기동은 아래와 같이 하면 된다. (참고)
$ heroku ps:scale worker = 1 // 서비스 기동
$ heroku ps:scale worker = 0 // 서비스 중지
모니터링
로그 확인은 heroku logs (—tail)
명령어를 이용하여 볼 수 있다.
처음에 테스트 겸 서비스를 기동시켰는데 결과가 안 나와서 로그를 까봤더니 오타가 원인이었다 (..)
정상 기동시켰더니 아래와 같이 의도한 대로 결과가 잘 나오는 것을 볼 수 있었다.
다 만들고 나서 안 사실인데, 네이버 검색도 그렇고, 사내에서 사용 중인 Confluence Wiki도 검색 쿼리를 날리는 방식이 같다. 이후에는 검색엔진을 선택할 수 있게 업데이트 해야겠다.
ex) 구글 검색! [키워드]
ex) 네이버 검색! [키워드]
ex) wiki 검색! [키워드]
느낀 점
slack bot 첫 구현 경험
→ 생각만 하는 것과 실제로 해본 것은 큰 차이가 있다. 간단한 봇이지만 실제로 만들어 봤으니 이제 앞으로 더 많은 것을 슬랙 봇으로 자동화할 생각이다. 기대된다.
TDD 더 익숙해진 것 같다는 느낌을 받음
- 자연스럽게 구현 방식보다는 테스트 케이스를 먼저 생각하게 됨
- 켄트 백이 이야기한 TDD의 장점들이 점점 와 닿는다.
Heroku 사용 경험
- 토이 프로젝트용으로 쓰기에 좋은 클라우드 서비스인 것 같다. 더 알아보고 여러 가지 올려봐야겠다. :)
댓글 영역