마지막 수정 2023.08.17
✅ 이 프로젝트는 뭐예요?
서론이라 넘기셔도 무방합니다!!
프로젝트를 할 때마다 느끼는 것 이지만, 새로운 기술들을 배우고 적용하는 것은 흥미롭고 재미있는 것 같습니다.
그래서 저의 노션 개인페이지에는 제가 앞으로 적용해보고 싶은 기술들이 리스트되어 있는데요! 그 중 Slack과 OpenAPI를 한 번 사용해보려고 합니다.
Slack을 이용해서 날씨봇을 만들어볼겁니다. 저는 집에서 나가기 전에 항상 날씨를 확인하고 나가기도 하고, 비가 오면 레이더로 확인하기도 하는데요, 사용하면서 은근하게 불편하다고 생각하는 내용들을 개선해보는 방향으로 만들어보려고 합니다.
제일 기본적으로는 기상청에서 제공하는 OpenAPI를 사용해서 단기예보 정보를 받아오고 이를 데이터베이스에 저장해놨다가, 요청이 들어오면 저장되어 있던 정보를 이용해서 답변을 해주려고 합니다. 문제는 데이터 갱신인데, 기상청의 단기예보는 하루에도 몇 번씩 일정한 간격으로 업데이트가 됩니다. 즉, 최신화 된 데이터를 가지고 있어야 하기 때문에 스케쥴러와 같은 기능을 사용해서 DB에 담겨있는 데이터도 최신화를 해주어야 할 것입니다.
몇일 전 날씨가 어땠는지 확인해볼 수 있는 기능도 넣어보려고 합니다. 환절기에는 하루만 지나도 날씨가 확확 바뀌는데 이럴 때마다 옷을 무엇을 입고 나가야할지 너무나 고민이 많이 되죠. (옷을 잘못 골라서 너무 더울 때에도 있었고, 너무 추울 때에도 있었습니다.) 심지어 몇일 집에 박혀있다가 나갈 때에는 너무나 곤란했습니다. 이럴 때 몇일 전 날씨와 비교해서 옷을 입고 나가려고 해도..! 인터넷에 검색을 해봐도 나오지 않습니다. 그래서 몇일 전 날씨도 확인해볼 수 있는 기능도 넣어보려고 합니다!
서론이 길었습니다! 이번 프로젝트는 Kotlin을 사용해보려고 합니다. 인턴을 하면서 nest.js를 사용하는데 함수형 프로그래밍이 생각보다 재미있어서 Java와 호환이 되는 Kotlin으로 Spring boot 프로젝트를 해보려고 합니다.
자 그럼 이제 Slack API를 이용해서 Slack bot을 만들어보고, 코드를 통해 bot이 특정 채널에 메시지를 보낼 수 있도록 해보겠습니다.
✅ Slack App 만들기
- https://api.slack.com/에 접속 후, Create New App → From scratch 선택
App name 작성 및 slack bot을 적용하고 싶은 workspace 선택 → Create App 선택 - Features-App Home 선택
- App Display Name-Edit: Bot의 이름 설정
- Show Tabs-Messages Tab
Direct messages your app sends will show in this tab 활성화
Allow users to send Slash commands and messages from the messages tab 체크
- Event Subscriptions 선택
- Subscribe to bot events: app_mention 선택 시, 사용자가 멘션을 하면 반응을 함
- OAuth & Permissions 선택
- Scopes: Bot Token Scopes에서 chat:write, chat:write.public, channel:read 추가
- install to workspace 이후 Bot User OAuth Token 복사
- Webhook 설정
- Features - Incoming Webhooks에서 Active Incoming Webhooks를 on으로 설정
- Webhook URLs for Your Workspace - Add New Webhook to Workspace 클릭
봇이 이야기를 할 채널 설정 및 생성 - Webhook URL 복사
- Settings
- Add features and functionality: Bots, Incoming Webhooks 추가
✅ Slack에 메세지 보내는 코드 작성
Kotlin 환경임을 고려하고 봐주세요!
1. 의존성 추가하기
우선 build.gradle.kts에서 slack에 대한 의존성을 추가해주고, Load Gradle Changes를 해주자. 코끼리 모양과 함께 새로고침 모양이 같이 있다면 그것이다!
// slack
implementation("com.slack.api:bolt:1.30.0")
implementation("com.slack.api:bolt-servlet:1.30.0")
implementation("com.slack.api:bolt-jetty:1.30.0")
2. yml 파일을 통해 환경변수 설정하기
application.yml 파일을 통해 bot-token, webhook-url 환경변수를 설정해주자.
application.yml에 모든 환경 변수를 때려박고 관리를 한다면 나중에 유지보수가 힘들어질 수 있기 때문에 yml 파일을 분리해주는 것이 좋다. slack과 관련된 환경 변수들은 application-slack.yml 이라는 파일에서 따로 관리해주자.
Spring boot에서는 application-{profile 명}.yml으로 yml 파일을 분리할 수 있는데, application.yml에서 해당 프로파일을 추가해주면 사용할 수 있다.
application-slack.yml을 생성하고 밑과 같이 적어준다. bot-token이라는 이름은 내가 임시로 작성한 것이기 때문에 다른 이름으로 바꿔도 무방하다.
bot-token에는 slack app 설정에서 Features - OAuth&Permissions의 Bot User OAuth Token을 적어주고,
Bot User OAuth Token은 xoxb- 형태로 이루어져있다.
slack:
bot-token: xoxb-어쩌구저쩌구-봇토큰
webhook-url: https://hooks.slack.com/services/어쩌구저쩌구
application.yml에는 밑과 같이 작성하여 slack 프로파일을 사용함을 명시한다.
spring:
profiles:
include: slack
3. Service 코드 작성하기
Slack과 연결이 잘 되었는지 간단한 코드를 작성해서 확인해보자.
1) yml 파일에서 bot-token 값 받기
Java때와 같이 @Value 어노테이션을 통해 값을 받을 수 있다. @Value(value = "\${값의 위치}")를 통해 값이 어디에 있는지 명시할 수 있다.
하지만 Kotlin에서의 변수는 기본적으로 정의(definition)과 함께 초기화를 해주어야 하기 때문에 앞에 lateinit 키워드를 적어주어야 한다.
2) Slack API Client 라이브러리를 이용한 메세지 보내기
https://slack.dev/java-slack-sdk/guides/composing-messages
위에 있는 링크를 보면 Java 코드 예시가 있는데, 이를 토대로 코드를 작성해보면 밑과 같다.
runCatching{}을 통해 메시지 전송에 실패한 경우 어떤 에러가 발생했는지 로그를 찍도록 했다.
@Service
@Transactional(readOnly = true)
class SlackService {
@Value(value = "\${slack.bot-token}")
lateinit var token:String
fun sendSlackMessage() {
val methods:MethodsClient = Slack.getInstance().methods(token)
runCatching {
methods.chatPostMessage(ChatPostMessageRequest.builder()
.channel("#오늘-날씨-어때")
.text("나무늘봇..... 메세지... 테스트....")
.build()
)
}.onFailure {err ->
Logger.log.info(err.message)
}
}
}
코드를 위와 같이 작성하고 요청을 보내보면, 밑과 같이 메시지 전송에 성공한 것을 볼 수 있다!
3) Webhook을 통한 메세지 전송
2)번과 같이 Slack Client API를 이용하는 방법도 있고, Webhook을 이용하는 방법도 있다.
먼저 위와 같이 @Value 어노테이션을 통해 yaml 파일에 적어주었던 Webhook URL을 받아온다.
Slack의 Instance를 받은 다음 .send() 메서드에 webhookUrl과 보내고자 하는 text를 전달해주면 해당 채널로 봇이 메세지를 보낸다.
.send()의 결과를 WebhookResponse 객체에 담기 때문에 만일 실패했다 하더라도 어떤 종류의 에러인지 확인할 수 있다.
@Service
@Transactional(readOnly = true)
class SlackService {
@Value("\${slack.webhook-url}")
lateinit var webhookUrl:String;
fun sendMessageByWebhook(){
runCatching {
val res:WebhookResponse = Slack.getInstance()
.send(webhookUrl, "{\"text\":\"안녕!!\"}")
}
.onFailure {
err -> Logger.log.info(err.message)
}
}
}
⚠️ 에러 종류
1. not_in_channel
slack bot이 해당 채널에 초대가 되어있지 않을 때 발생하는 현상이라고 한다. OAuth & Permissions의 Bot Token Scopes에서 chat:write.public 권한도 주어야 위 에러가 발생하지 않는다.
🧐 생각 n줄
프로젝트를 하면서 조금씩 느끼고 있는 것이 있습니다. 제가 생각하기에 저는 새로운 것을 도전하는 것을 별로 안 좋아하는 사람인줄 알았는데요, 몰랐던 것을 스스로 찾아보고 적용하면서 기능을 하나하나 구현해보니 이 만큼 뿌듯하고 재미있는 것이 또 없는 것 같습니다 😆