Daily Schedule Slack Bot (with flex)

유영석·2022년 2월 23일
12

Daily Schedule Slack Bot

목록 보기
1/2

작은 스타트업에 근무하면서 회사에서는 자율적으로 재택근무를 선택해서 할 수 있는 혜택을 주었다.
하지만 막상 출근했을때 누가 출근했고 누가 재택근무를 하는지 확인하는지를 체크하기가 불편해서 항상 Flex(HR 근태관리솔루션)에 들어가서 확인해야했다.
매번 들어가서 확인하는것이 생각보다 불편해서 슬랙에 데일리 스케쥴러를 만들어보았다 :)

결과물은 다음과 같다.
휴무일을 제외한 매일 10시에 특정 슬랙 채널에 이러한 형태로 알람이 간다.
슬랙 알림 예제 화면

결과물은 간단하지만 생각보다 다양한 기술들을 사용했다.
다행히 크롤링은 따로 사용하지 않았다.

  • Node.js
  • GCP(Function, Cloud Scheduler)
  • Slack (Webhook)
  • Flex API

전체적인 로직은 다음과 같다.
1) GCP Cloud Scheduler 에서 설정한 시간(매일 10시)에 GCP Function을 호출한다.

Cloud Scheduler는 cron job schedule로, 매일 10시에 실행시키기 위해 빈도를 다음과 같이 설정했다.
0 10 * * * (Asia/Seoul)

2) GCP Function 내에 Node.js 코드를 실행한다.

  1. Flex API를 이용하여 로그인(JWT) 및 근태정보를 획득한다.
  2. 근태정보와 회사 구성원 정보를 조합하여 팀별로 분류한다.
  3. 2에서 얻은 데이터를 이용하여 Slack를 제작한다.
  4. Slack Webhook을 이용하여 메세지를 보내고 종료한다.

전체 코드

const axios = require("axios");

const departmentsID = "departmentsID";
const loginURL = "https://flex.team/actions/login";
const upcomingURL = `https://flex.team/actions/api/v1/departments/${departmentsID}/upcoming-events?countLimitType=DAY&limitCount=1`;
const userListURL = "https://flex.team/actions/people/list?size=50&page=0";
const webhookURL = "https://hooks.slack.com/services/...";

exports.helloWorld = async (req, res) => {
  timetable().then();
  res.status(200).send("Hello World!");
};

const timetable = async () => {
  try {
    const isWeekend = new Date().getDay() === 0 || new Date().getDay() === 6;
    if (isWeekend) return; // 주말 예외 처리

    const { accessToken, refreshToken } = await axios
      .post(loginURL, {
        email: "Flex-Email",
        password: "Flex-Password",
      })
      .then((res) => res.data.credentials);

    const events = await axios
      .get(upcomingURL, {
        headers: { cookie: `AID=${accessToken}; RID=${refreshToken}` },
      })
      .then((res) => res.data.data.events[0].events);

    // 휴일인 경우 종료
    if (events.findIndex((event) => event.eventType === "CUSTOMER_HOLIDAY") > 0)
      return;

    const users = await axios
      .get(userListURL, {
        headers: { cookie: `AID=${accessToken}; RID=${refreshToken}` },
      })
      .then((res) => res.data.users);

    events.forEach((event) => {
      const uid = event.eventCta.link.replace("/feed?uid=", "");
      const user = users.find((user) => user.uuid === uid);
      if (user) event.departmentName = user.departments[0].name;
    });

    const groupByDepartment = events.reduce((acc, obj) => {
      const key = obj.departmentName;
      if (!acc[key]) acc[key] = [];
      acc[key].push(obj);
      return acc;
    }, {});

    const today = formatDate();

    let message = `*${today}*\n`;
    Object.entries(groupByDepartment).forEach(([departmentName, events]) => {
      message += `>${departmentName}\n`;
      events.forEach((event) => {
        message += `:${event.eventEmoji}: ${event.eventName} \`${event.eventDescription}\`\n`;
      });
    });

    await axios.post(webhookURL, { text: message });
  } catch (error) {
    throw error;
  }
};

function formatDate(date = new Date()) {
  const d = date instanceof Date ? date : new Date();
  let month = "" + (d.getMonth() + 1);
  let day = "" + d.getDate();
  const year = d.getFullYear();

  if (month.length < 2) month = "0" + month;
  if (day.length < 2) day = "0" + day;

  return [year, month, day].join(".");
}

코드 제작하면서 신경쓴 점

  • 휴일에 알람이 가지않도록 예외처리를 한다.
  • 이벤트 리스트에서 CUSTOMER_HOLIDAY 인 경우에도 알람이 가지않도록 예외처리를 한다.
  • Flex API를 직접 호출하기 위해 Flex 내에서 Token 사용위치를 분석했다.
    (Flex API에서는 cookie에 accessToken, refreshToken을 넣는다.)
  • 개인별 조직명과 이벤트를 잘 merge하여 데이터를 만든다.
  • Flex에서 사용하는 Emoji 이름이 Slack에서 바로 사용하는지 미리 체크한다.
    (다행히도 같은 이름을 써서 바로 따로 매칭용 Object를 만들지 않았다.)
  • 날짜 표시를 이쁘게 하기위해 앞에 padding 함수를 넣어주었다.
  • 회사는 탄력근무제를 시행하고 있어서 근무시간과 휴가시간을 함께 표시했다.

느낀점
여러 사람들에게 편리한 기능을 만들어 제공해서 뿌듯했다. 약 1년 정도 사용하면서 나름 편리했던 나만의 슬랙 봇이라고 생각한다 :-) 다음에 또 이런 것을 만들 기회가 있다면 즐겁게 만들어야지~

profile
ENFP FE 개발자 :)

2개의 댓글

comment-user-thumbnail
2022년 3월 2일

좋은 봇이네요! notion API 를 사용해서 notion 인라인 테이블 뷰로 표기해보시는것도 좋을 것 같아요 ㅎㅎ
좋은 글 감사합니다!

1개의 답글