본문 바로가기

main/Common

슬랙봇으로 피크민 챌린지 랜덤 조 편성: 매주 반복되는 작업 자동화하기

오랜만에 재밌는 개발을 했어요!

 

💐 피크민 블룸

피크민 블룸이란 포켓몬고의 피크민버전이라고 생각하면 쉬운데

걸으면서 피크민을 키우고(?) 꽃을 심고 버섯을 패는(!) 등의 활동을 기본으로 하게 됩니다.

 

또 친구와 엽서를 주고받을 수 있으며

개개인별로 하루동안 걸은 경로와 걸음 수로 짧게 기록을 남겨 주는데 꽤 재밌습니다.!

 

그리고, 매주 챌린지를 할 수 있는데 5명씩 모여 100,000걸음을 걷고, 꽃을 30,000송이 심는 것이 있어요.

이는 친구와 함께 진행할 수 있어서 글또 10기 슬랙의 피크민또 채널에서 매주 월요일 인원을 모집하고 함께 진행해오고 있었습니다.

 

💫 사건의 발단

그런데 가만보니 이 챌린지를 모집하고 진행하는 일이

굉장히 별 것 아닌 일이지만 꽤나 번거로워보였어요.

 

매주 월요일 아침.. 댓글의 사용자를 모두 수집하고 모든 조가 5명에 가까워지도록 랜덤 매칭을 합니다.

여기까지는 약간의 비용이 들긴 하지만 GPT 등의 AI가 요즘은 잘 도와주긴 하죠..

 

그렇다면 선정 된 5명 중에는 누가 나서서 파티를 열고 팀원들을 초대할까요?

조장을 선정해야 하는데 뉴비보다는 오비를 선정하는 것이 좋을 것 같아요.

 

그 후 이 과정을 정리해서 댓글을 작성해 모든 사람들에게 알리는 것 까지가 한 프로세스인데,

평균적으로 매주 20명~30명 정도가 참여하니까..

 

이 프로세스를 자동화하면 짐을 덜어 드릴 수 있겠다는 생각이 들었습니다.

그리고 재밌을 것 같았어요 -.

 

📗 진행 과정

피크민또 싸장님(채널 생성자)과 DM 후 작업을 시작해보기로 했습니다.

 

먼저 기능 명세 초안을 작성했는데 개발 중 (굳이?) 싶은 건 깎아내기도 하고

결국은 중요한 기능만 골라 배포하게 되었습니다.

 

슬랙봇 개발은 처음이다보니 먼저 사전조사를 좀 해봤습니다.

조사해보니 어떤 언어나 플랫폼이냐에 상관없이 API를 호출하는 방법을 사용해도 되고,

혹은 Socket을 열어 호출하는 방식으로 JavaScript, Python, Java 언어로 메서드를 제공해주고 있어서 편리하게 사용할 수 있었어요.

또, 데이터 저장에는 많이들 스프레드시트를 사용하시는 것 같았는데

일단 제가 저장할 데이터는 누군가에게 공유되는 것도 아니고 내부적으로 읽고 쓰기만 하면 되었습니다.

 

그렇다면 ... 역시 JavaScript에서 JSON 파일을 사용하는 것이 익숙하므로

저는 JSON파일로 유저별 데이터를 저장하기로 했습니다.

 

처음에는 서버를 어떻게 해야되나? 부터 고민을 했는데

개발하고보니 이게 서버가 필요한가? 라는 생각을 했습니다.

 

물론 있으면 편하기야 하겠지만 요정도는 내 컴퓨터 하루 안 꺼놓는 정도로 가능하지 않나..싶고

일단은 서버를 따로 올리지 않았어요.

 

그리고 역시 개발보다 중요한건 브랜딩과 디자인 아니겠어요 ...?

하지만 그게 내가 제일 못하는 두가지 🥺라서 싸장님께 help 쳤는데

센스있는 작명과 더불어 너무 ㄱㅇㅇ 그림을 그려주셨습니다.

그렇게 탄생한 포루라기

설명: 게임 내에서 피크민을 부를 때 호루라기를 분다.

TMI: 호루라기라는 말은 '호로로'라는 의성어에서 유래했으며, 원래는 살구 씨에 구멍을 내고 공기를 불어 소리를 내는 도구를 가리키는 순우리말이다.

 

👩🏼‍💻 개발 과정

먼저 테스트를 위한 워크스페이스를 생성했고

해당 워크스페이스에 슬랙봇을 만들기 시작합니다.

 

OAuth & Permissions 탭에서 봇에게 권한을 줍니다.!

일단 뭐가 필요할 지 처음에는 정확히 몰라서 좌측의 요런 것들을 주었고

API 문서를 읽다보니 해당 API를 사용할 때 어떤 권한이 필요한지 알려주고 있어

중간중간 추가해줬습니다.

 

개발 중 권한이 없거나 토큰 값이 잘못되거나 하는 경우 슬랙API가 에러 메시지로 친절하게 알려줍니다.

 

Install to Workspace 버튼을 찾아 눌러 권한을 허용해주면, 슬랙에 봇이 설치됩니다.

근데 본인의 워크스페이스가 아닐 경우 Request Submit 과정이 있을 수 있는데

그러면 권한이 추가되고 Reinstall할 때마다 요청을 해야 해서 번거로울 수 있음..!! 한번에 하면 좋을 것 같습니당.

 

 

전체 코드는 여기 레포지토리에 있습니다. 일부만 가져와서 설명하자면

// app.js
(async () => {
  await app.start(process.env.PORT || 3000);
  app.logger.info("⚡️ Bolt app is running!");

  const CHANNER_ID = await findChannelId(app, CHANNER_NAME);

  await updateUserActivity(app, CHANNER_ID);

  await findMessageInfo(app, CHANNER_ID, WALK_TEXT);
  await findMessageInfo(app, CHANNER_ID, FLOWER_TEXT);

  app.logger.info("2초 후 프로그램 종료...");
  setTimeout(() => {
    process.exit(0);
  }, 2000);
})();

각종 키값을 연결해서 app.start를 한 후

 

피크민또 채널을 찾고 -> 유저의 액티비티(메시지, 리플, 리액션)를 업데이트 ->

모집 스레드 메시지 두개를 찾아서 -> 각각 조원 편성 후 리플 작성 -> 끝.

 

보다시피 await이 난무하는 것을 볼 수 있는데...

UI개발이 아니라 자동화다보니 순서를 지켜야하는 과정이 많았어요 ㅋㅋ

 

// controllers/main.js
const findChannelID = async (app, name) => {
  const conversationList = await app.client.conversations.list({
    token: process.env.SLACK_BOT_TOKEN,
    limit: 500,
  });
  let channelID;
  ...

// controllers/user.js
const updateUserActivity = async (app, channelID) => {
  const prevData = await readJSONFile();

  const conversationList = await app.client.conversations.history({
    channel: channelID,
    oldest: prevData.lastDate,
    inclusive: false,
    limit: 999,
  });

conversations에서 채널ID와 message 정보를 조회하는 코드.

처음에는 기본 limit이 걸려있는 줄 모르고 데이터 조회가 안 돼서 잠깐 삽질을. ^__^ 999개가 최대

 

999개를 가져올 수 있다고 해도

매번 모든 대화를 불러오지 않고 마지막 조회한 시간을 저장해둔 뒤

다음 조회할 때 oldest 값을 통해 마지막 조회 시간 이후 메시지만 불러오도록 구현했습니다.

 

처음 유저의 활동내역을 만들기 위해 데이터를 가져올 때는 위와 같이 리밋을 해제해서 많이 조회했지만

모집 스레드 타겟을 찾을 때는 24시간 이내의 메시지에서만 찾아도 관계없기에 아래와 같이 구현했습니다. (UNIX시간)

// controllers/main.js
const findMessageInfo = async (app, channelID, text) => {
  const oneDayAgoTS = (Date.now() - 24 * 60 * 60 * 1000) / 1000;
  const result = await app.client.conversations.history({
    channel: channelID,
    oldest: oneDayAgoTS,
  });
  const conversationHistories = JSON.parse(JSON.stringify(result));
  ...

 

그 다음 조원을 편성합니다.

전체 랜덤 셔플을 한 번 돌린 후

const getRandomUsers = (users, max) => {
  for (let i = max; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [users[i], users[j]] = [users[j], users[i]];
  }
  return users;
};

어느 한 조의 인원수가 모자라지 않도록 골고루 분배되게 했습니다.

const teamCount = Math.ceil(replyUsersCount / MAX_PEOPLE);
const teamMembers = Array.from(Array(teamCount), () => Array(MAX_PEOPLE));

let i = 0;
let j = 0;
randomReplyUsers.map((user) => {
  if (i === teamCount) {
    i = 0;
    j++;
  }

  teamMembers[i][j] = user;
  i++;
});

 

그리고 조장은 각각 유저별로 messages, replies, reactions, history 값 등을 모두 더하고

한 조 내에서 정렬한 뒤 중앙값 이상의 유저들을 또다시 랜덤 셔플하여, 

이때 첫 인덱스에 있는 사람으로 뽑았습니다.

중앙값인 12 이상의 데이터는 계속 섞이고 6은 남아있는 것을 볼 수 있습니다. 

 

이렇게 조원과 조장이 모두 정해지면 텍스트를 가공해주고 슬랙 메시지로 전송합니다!

요런 너낌

 

마지막으로, 참여하는 유저의 활동 데이터에 참여 이력을 더해주면서 ,

다음 조회 시 점수에 반영될 수 있도록 처리 해두는 것으로 마무리합니다.

 

 

정리하자면,

1. 모든 조가 5명에 가까워지도록 분배

2. 조원 랜덤 편성 뒤 활동 점수 계산하여 내림차순 정렬

3. 해당 조 활동 점수의 중앙값 이상 사람들을 조장 후보로 두고 랜덤 셔플

4. 리스트 0번으로 선택된 사람이 조장이 됨

 

배치 돌리기

매주 월요일 오전 8시에 자동으로 실행될 수 있도록

# 에디터 작성
crontab -e

# 작성된 내용 조회
crontab -l

# node 설치 경로 찾기 
which node

# 현재 파일 경로 찾기
pwd

 

crontab -e로 에디터를 열어서

시간날짜 (환경변수) [node 경로] [실행시킬 파일경로] >> [로그파일경로] 2>&1

 

크론탭은 리눅스 기반이니까 맥에서 원래 잘 돌고

뭐... 명령어는 다 생각안나서 구글링하다가.. GPT가 가르쳐주는대로 설정해주었습니다ㅏ 😊

 

배포 후기

아이디어 생각하고는 재밌을 것 같아서 바로 실행에 옮겼지만

기획서 실컷 써놓고 개발 못하면 어떡하지 ㅎㅎ ,, 채널에 자동화 하겠다고 던져놨는데 못하면 ...어떡하지!!!

두려움 살짝 안고 갔는데

슬랙봇 API가 정말 잘돼있어서 쓰는대로 잘 되었던 것 같아요.

 

어떻게보면 좀 복잡한 로직이었는데 쉬운 알고리즘 푸는 정도의 재미였고

랜덤 구현하면서 무한 while 말고 다른 방법 없나.. 하며 알고리즘을 찾아보다

Fisher-Yates Shuffle 이라는 것도 알게 됐습니다.

 

또 비동기로 자동화를 짜면서 덕분에 Promise도 이렇게나 많이 쓰고 오히려좋아..

 

개발은 정말 재밌게 했고.

 

 

기획 아이디어 정리, 각종 질문 공세에 대응, 직접 그린 그림 선사 등등 많은 도움을 주신 싸장님 외에도

피크민 채널의 14분께서 베타 테스트를 위한 워크스페이스에 들어와주셨고 데이터 수집에 도움을 주셨습니다.

 

슬랙봇은 임의로 목 데이터를 어떻게 넣어야할 지 모르겠어서 (유저 계정을 막 생성할 수는 없잖아요 ..?!?!)

실제 데이터를 통해 시도해보았는데 모두 흔쾌히 도와주셔서 감사했답니다.!!!!!

 

또 글또 내에서 이미 슬랙봇을 개발하신 분들의 후기가 처음 시작해서 막막할 때 도움이 많이 되었습니다ㅏ

처음 써보는 언어로 슬랙봇 만들기

슬랙 봇이 보낸 메시지를 '더 쉽게' 수정해봅시다

SIPE 커뮤니티를 위한 슬랙 봇 만들기 (feat. 사이프톤)

 


순수 투입 시간만 따졌을 때 대략 기획서 작성 1일, 개발 4일, 정도 소요된 것 같습니당.

ㅋ ㅋ ! 앱 배포는 역시 짜릿해

 

그럼 이제 포루라기가 매주 열일 해주길 바라며

피크민 같이 하실래요?

'main > Common' 카테고리의 다른 글

Web Rendering Patterns 조금 자세하게 알아볼까  (0) 2024.02.04