[포스코x코딩온] KDT-Web-8 10~11주차 팀프로젝트 Do IT - 슬랙봇 전체 생성과정

Yunes·2023년 9월 15일
0

[포스코x코딩온]

목록 보기
30/47
post-thumbnail

서론

슬랙의 SocketMode, Webhook 을 활용해서 특정 이벤트 발생에 대해 커스텀 콜백을 반환하도록 했다.

본 프로젝트에선 사용자가 스터디 개설을 요청하는 이벤트가 발생할때 이후 슬랙에 전송된 스레드상에서 어떤 버튼을 클릭하는지에 따라 다른 콜백함수를 실행하도록 하여 어드민의 기능을 (스터디 개설 허용 판단) 슬랙봇을 통해 구현했다.

필요한 설정

슬랙봇 생성

slack api 에 접속해보면 Your Apps 를 통해 앱을 새로 생성해줄 수 있다.

From scratch 클릭

App Name 을 지정하고 설치할 슬랙의 워크스페이스를 선택하여 앱을 생성한다.

서버 설정

interactive features 를 사용하여 슬랙에 전송한 메세지에서 action 을 인식하기 위해 bolt.js 와 여러 토큰들을 생성해야 한다.

bolt.js 라이브러리는 기존에 action, webapi, eventapi... 등 슬랙 API 가 분산되어 있던 것을 하나의 라이브러리에서 모두 제어할 수 있도록 만들어진 라이브러리다.

bolt.js 는 JavaScript, Python, Java SDK 를 제공한다.

npm install @slack/bolt

기본 서버 설정은 다음과 같다.

const { App } = require('@slack/bolt');

// Initializes your app with your bot token and signing secret
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

(async () => {
  // Start your app
  await app.start(process.env.PORT || 3000);

  console.log('⚡️ Bolt app is running!');
})();

그런데 이번 프로젝트는 express 를 사용하고 있다. 그래서 bolt.js 를 통해서도 서버를 열어줄 수 있으나 http, express, bolt.js 를 동시에 사용하기 위해서 다음과 같이 코드를 구성했다.

// slack.js
const { App, LogLevel } = require("@slack/bolt");
const { Study } = require("./models");

let logLevel = LogLevel.INFO;

const {
  SLACK_BOT_TOKEN: token,
  SLACK_APP_TOKEN: appToken,
  SLACK_SIGNING_SECRET: signingSecret,
} = process.env;

const boltApp = new App({
  token,
  logLevel,
  signingSecret,
  socketMode: true,
  appToken,
});

// ... action 구현

(async () => {
  const port = 3000;
  // Start your app
  await boltApp.start(process.env.PORT || port);
  console.log(`⚡️ Slack Bolt app is running on port ${port}!`);
})();

module.exports = boltApp;

먼저 slack 관련 진입점 설정을 위한 slack.js 코드를 작성한다. 이때 슬랙을 위한 서버도 돌아가야 해서 3000번 포트를 사용하여 bolt 서버를 즉시 돌려준다. 여기에 boltApp 을 통해 action 들을 설정하여 slack 에서 버튼을 입력하거나 slash command 를 입력하거나 혹은 shortcut 등의 이벤트가 발생하는 특정 이벤트들에 대해 어떤 콜백을 반환할지 지정할 수 있다.

// index.js
const http = require("http");
const express = require("express");

const app = express();
const boltApp = require("./slack");

const server = http.createServer(app);

// ... router 및 static 등 설정

app.use("/slack/events", (req, res) => {
  boltApp.receiver.requestListener(req, res);
});

db.sequelize.sync({ force: false }).then(async () => {
  server.listen(PORT, () => {
    console.log(`http://localhost:${PORT}`);
  });
});

그러고 express 와 http 사용하는 루트 파일인 index.js 에서 boltApp 을 require 로 가져와서 router 만 설정한다.

// CStudy.js

// ...

await boltApp.client.chat.postMessage({
  token: process.env.SLACK_BOT_TOKEN,
  channel: process.env.CHANNEL_ID,
  blocks,
  text: "스터디 개설 요청",
});

위의 코드는 controller 에서 특정 API 를 실행시 슬랙에 알림 스레드를 전송하기 위한 코드이다.
저 blocks 는 다음 코드를 통해 만들어줬는데 형태는 slack 의 block-kit 을 활용했다.

function definePayload(
  memTotal,
  startDate,
  endDate,
  title,
  intro,
  category,
  studyId,
  link
) {
  return {
    blocks: [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: "스터디 개설이 요청되었습니다.",
        },
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: `*타이틀:*\n${title}`,
          },
          {
            type: "mrkdwn",
            text: `*링크:*\n${link}`,
          },
          {
            type: "mrkdwn",
            text: `*시작일:*\n${startDate}`,
          },
          {
            type: "mrkdwn",
            text: `*종료일:*\n${endDate}`,
          },
          {
            type: "mrkdwn",
            text: `*정원:*\n${memTotal}`,
          },
          {
            type: "mrkdwn",
            text: `*카테고리:*\n${category}`,
          },
        ],
      },
      {
        type: "section",
        text: {
          type: "plain_text",
          text: `소개글:\n${intro}`,
          emoji: true,
        },
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "승낙하기",
            },
            action_id: "approve",
            style: "primary",
            value: `${studyId}`,
          },
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "거절하기",
            },
            action_id: "reject",
            style: "danger",
            value: `${studyId}`,
          },
        ],
      },
    ],
  };
}

module.exports = definePayload;

정리
1. slack entry point 생성 및 bolt 서버 가동
2. express 와 bolt 서버 동시에 실행
3. index.js 에 slack receiver 및 router 등록
4. controller 에 특정 이벤트 발생시 slack 으로 소켓 전송
5. 슬랙에서 웹훅을 통해 특정 action 실행시 slack.js 에 설정해둔 커스텀 콜백을 실행

이 과정에서 SLACK_BOT_TOKEN, CHANNEL_ID, SLACK_APP_TOKEN, SLACK_SIGNING_SECRET 값이 필요하다.

슬랙봇 설정

Settings - Basic Information

이 페이지에선 Incoming Webhooks, Event Subscriptions, Bots, Permissions 설정이 필요하고 하단에서

SLACK_SIGNING_SECRET 를 얻을 수 있다.

  1. Incoming Webhooks

  2. Event Subscriptions

Enable Events 를 토글 On 시켜주고 Subscribe to bot events 에서 message.im 이벤트를 추가한다.

  1. Bots

앱의 display name, default name 을 지정한다.

  1. Permissions

Permissions 는 다음 과정을 거친 이후에 확인하는 편이 좋다.

  1. App-Level Tokens SLACK_APP_TOKEN

Basic Information 에서 아래로 스크롤하다보면 App-Level Tokens 가 나온다. 여기서 바로 Generate Token and Scopes 를 해서 SLACK_APP_TOKEN 를 생성해도 좋고 아니면

Settings - Socket Mode 로 들어가서 Connect using Socket Mode 에서 Enable Socket Mode 를 On 으로 변환후 하단에 3가지 Features 를 추가한다.

이때 App-Level Token 을 생성하라는 화면이 나왔던 것 같다. 그래서 Basic Information 에서 직접 SLACK_APP_TOKEN 를 만들거나 Socket Mode 를 켜면서 SLACK_APP_TOKEN 를 생성해도 된다.

  1. Permissions SLACK_BOT_TOKEN

다시 Permissions 를 클릭하면 Features 에 OAuth & Permissions 로 화면이 전환된다.

여기서 SLACK_BOT_TOKEN 를 획득할 수 있다.

만약 APP_LEVEL Token 이 생성되어 있지 않다면 위의 페이지에서 Bot User OAuth Token 만 보일 것이다.

슬랙은 토큰 종류에 따라 앞에 붙는 prefix 가 다르다.
SLACK_BOT_TOKEN : xoxp-@@@
SLACK_APP_TOKEN : xapp-@@@

OAuth & Permissions 에서 아래로 스크롤하다보면 Scopes 설정하는 부분을 볼 수 있다.

여기서 슬랙봇이 할 수 있는 범위를 지정할 수 있다.

Bot Token Scopes

User Token Scopes

Features - Interactivity & Shortcuts

마지막으로 Interactivity 토글을 On 시켜줘야 한다.

이게 On 상태로 토글되어야 슬랙에서 버튼을 클릭할때 서버에서 웹훅을 통해 커스텀 콜백을 실행시킬 수 있다.

사실 아직 구하지 않은게 있다. 바로 CHANNEL_ID 슬랙 채널 아이디인데 이건 슬랙에서 보이는 채널 ID 와 다르다.

채널 클릭할때 뜨는 위의 Channel ID 가 아니다.

먼저 슬랙봇을 생성했으면 슬랙에서 알림을 전달하고자 하는 채널을 생성하고 생성한 슬랙봇을 선택한다.

그러고 나면 특정 채널에 슬랙봇을 추가해준다.

이후 slack api 의 conversation.list 를 통해 특정 슬랙봇이 포함되어 있는 슬랙 채널의 ID 를 얻을 수 있다.

여기서 Tester 클릭, provide your own token 에 SLACK_BOT_TOKEN 을 넣고

아래에 비밀 채널로 만들어서 types 를 private_channel 로 설정후 Test method 를 실행하면

이렇게 CHANNEL_ID 를 얻을 수 있다.

웹훅 설정

bolt.js 가 제공하는 웹훅 기능이 다음과 같다.

// Listen for an event from the Events API
app.event(eventType, fn);

// Convenience method to listen to only `message` events using a string or RegExp
app.message([pattern ,] fn);

// Listen for an action from a Block Kit element (buttons, select menus, date pickers, etc)
app.action(actionId, fn);

// Listen for dialog submissions
app.action({ callback_id: callbackId }, fn);

// Listen for a global or message shortcuts
app.shortcut(callbackId, fn);

// Listen for slash commands
app.command(commandName, fn);

// Listen for view_submission modal events
app.view(callbackId, fn);

// Listen for options requests (from select menus with an external data source)
app.options(actionId, fn);

이중엔 서버에서 슬랙으로 스레드를 보내고 슬랙에서 버튼을 클릭하는 action 을 인지하고 콜백을 실행하고 싶어서 action 을 사용했다.

// slack.js
boltApp.action("approve", async ({ ack, body }) => {
  await ack();

  body.actions.forEach(async (action) => {
    const id = action.value;

    const beforeStudyStatus = (await Study.findByPk(id)).status;

    try {
      const res = await Study.update(
        {
          status: "ALLOWED",
        },
        { where: { id } }
      );

      const afterStudyStatus = (await Study.findByPk(id)).status;

      if (beforeStudyStatus !== afterStudyStatus)
        postMessage("스터디 개설을 승낙합니다.", body);
    } catch (error) {
      console.error(error);
    }
  });
});

코드가 위와 같은데 action 의 첫 파라미터가 action_id 이다. 이는 위에서 slack block kit 을 통해 blocks 를 어떻게 구성했다고 한 부분중 action 에 넣었고 어떤 action 을 실행할때 어떤 studyid 에 대해 상태를 변경하도록 하고 싶어서 action.value 를 통해 studyId 를 가져올 수 있었다.

이렇게 슬랙봇 전체 시스템을 만들었다.

결과

참고 레퍼런스

docs
slack block kit builder
bolt.js github
slack api
slack api tutorial ⭐️ -> 다양한 사례의 코드를 확인할 수 있어서 매우 유용하다.
reference
Log Rocket <Build a Slackbot in Node.js with Slack’s Bolt API>
blog
Kurly Tech Blog - slack block kit 활용

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글