딥상어동의 딥한 프로그래밍/엔지니어링

우당탕탕 슬랙 메시지 저장기(1) - 슬랙 메시지 넌 누구냐?

딥상어동의 딥한생각 2023. 5. 7. 23:39
슬랙 무료 플랜의 경우, 90일 단위로 주기적으로 메시지 함을 비웁니다.
이에 따라 추후 백업 및 활용 용도로 메시지를 저장하는 프로젝트를 진행했었습니다.

 

※음슴체주의


- 슬랙에서 데이터를 저장하는 방법은 크게 두 가지가 있음

- 하나는 설정 및 관리 → 워크 스페이스 설정 → 데이터 가져오기/내보내기 → 데이터 내보내기 

- 다른 하나는 API를 이용하는 방법.

https://api.slack.com/methods/conversations.history 

- API를 이용해 슬랙 데이터를 저장하는 방법에 대해 시리즈 글로 남길 예정.

 

(본 글은 성윤님께서 진행하신 genie 프로젝트를 기반으로 작성하였습니다.)

https://github.com/geultto/genie

 

GitHub - geultto/genie: 글또의 요정 지니 프로젝트

글또의 요정 지니 프로젝트. Contribute to geultto/genie development by creating an account on GitHub.

github.com


슬랙 앱 생성


- API를 그냥 사용할 수는 없음. 많은 것들이 준비되어 있어야함. 우선 활성화된 커뮤니티... 커뮤니티... 커뮤니티..(글또..짱...⭐️⭐️)

- 활성화된 커뮤니티가 있다면, 다음으로는 앱을 생성해야함.

 

https://api.slack.com/apps

- 우선 앱 페이지에 들어가주자. → 앱 생성 → Features에서 OAuth&Permissions 클릭 

1. Redirect URLs생성(필자는 아무 페이지나 입력했음)

2. 사용 Token종류

- Token에는 크게 두 가지 종류가 있음.

- User OAuth Token | Bot User OAuth Token 우리는 무슨 토큰을 써야 하는가? 일단. 우리의 목적은 슬랙 앱 데이터를 저장하는 것임

https://api.slack.com/authentication/token-types

- 이 내용은 token-types에 잘 정리되어 있는데, 해당 문서에 따르면

User tokens gain the "old world" resource-based OAuth scopes requested in the installation process (example: asking for channels:history grants a user token access to conversations.history for any public channel)

- history API를 사용하려면 User token을 이용하라고 가이드 되어 있다. 따라서, 메시지 데이터를 저장하는 용도로는 User OAuth Token을 사용하면 된다.

근데 잠깐!!!!! 여기서 그냥 Token을 사용하면 안됨. 

3. 앱 사용 권한 설정하기

https://api.slack.com/methods/conversations.history

- Slack API문서를 살펴보면 Required scopes라는게 있음. "사용기능별로 신청권한"이 다르다는 의미.

- 따라서, Scopes에서 필요한 기능들을 먼저 요청해야 한다.

4. 워크스페이스에 앱 설치

- 여기까지 하고 나서 워크스페이스에 앱 설치하면됨.

- 근데, 중간 중간 권한 Scopes에서 기능들을 추가할때마다 앱도 다시 설치해준다고 생각하면됨.


파이프라인 구조

- 일별로 유저/채널 리스트가 다를 수 있음. 그렇기 때문에 유저/채널 리스트를 먼저 업데이트 한 후, 메시지를 저장.

- 유저/채널 리스트가 필요한 이유는 메시지 내의 채널, 유저는 모두 ID로 되어 있어 별도의 식별자가 필요함.

- 그리고, 채널은 메시지 API 요청 시 파라미터로 넣어줘야함.

- 그래서, 유저/채널 리스트를 먼저 업데이트 하고 메시지를 업데이트


유저/채널 리스트 업데이트


- history API를 사용하기 위해서는 두 가지 파라미터가 (필수적으로)필요함. 하나는 token, 하나는 channel ID

- Token은 아까 위에서 말했고.. 그럼 channel ID를 가져와야함. 해당 API를 사용하면 채널 리스트를 다 가져올 수 있음.

https://api.slack.com/methods/conversations.list

- 테스터에 User Token을 넣으면 아래와 같이 채널아이디와 이름을 확인할 수 있음 

- 이 채널 리스트를 처리해서 Conversation_history의 파라미터로 사용. 

import ssl
import certifi

from slack_sdk import WebClient
from slack_bolt import App

# ssl 인증 에러가 발생하는 경우가 있어 추가
ssl_context = ssl.create_default_context(cafile=certifi.where())
app = App(client=WebClient(token=token, ssl=ssl_context))

channels = app.client.conversations_list()["channels"]

channel_sample = {
	"channel_id": channels[0]["id"],
    "channel_name": channel[0]["name"],
    "num_member": int(channel[0]["num_members"]),
    }

- 그럼 위와 같이 채널id와 채널 이름 그리고, 채널에 속한 사람들의 숫자를 알 수 있음.

 

- 유저리스트는 users_list API를 이용하여 업데이트 가능

users = app.client.users_list()["members"]

user_sample = {
	"user_id": users[0]["id"],
    "real_name": users[0]["profile"]["real_name"],
    "display_name": users[0]["profile"]["display_name"],
    }

- 위와 같이 user_id그리고 real_name과 display_name을 알 수 있음. 

- 참고로 real_name은 성명에 해당하고, display_name은 표시 이름에 해당함


메시지 업데이트


무엇을 수집할 것인가?

글또를 하시면 생일 축하를 받으실 수 있으십니다..?

- 슬랙의 대화는 기본적으로 게시글/쓰레드(댓글)/이모지 세 가지 구조를 가지고 있음.

- 각각의 스키마에 대해 가볍게 살펴보자.

게시글의 스키마

text: 메시지 정보
user: 유저아이디
ts: 게시글 작성시간(유닉스타임)
reactions: 이모지와 이모지를 남긴 유저 ID 및 카운트 수가 남음

 

- 반면, 쓰레드의 스키마는 거의 비슷하지만 thread_ts와 parent_user_id가 추가적으로 남음.

text: 메시지 정보
user: 쓰레드 작성 유저아이디
ts: 쓰레드 작성시간(유닉스타임)
reactions: 이모지와 이모지를 남긴 유저 ID 및 카운트 수가 남음
thread_ts: 게시글 작성시간(유닉스타임)
parent_user_id: 게시글 작성 유저 id

- thread_ts(게시글 작성시간 유닉스타임)이 167477261.653539로 위 게시글의 ts와 동일한 것을 알 수 있음

- 그렇다면 여기서 질문...

 

게시글 + 쓰레드를 묶을 수 있는 키 값을 무엇으로 사용해야 하는가?

- 그것은 바로. 게시글 작성 시간과 작성 유저아이디를 묶어주는 것.

- 게시글에서는 user + ts, 쓰레드에서는 thread_ts + parent_user_id 이렇게 묶어주면됨.

- 나는 아래와 같이 사용함.

# 게시글에서 게시글 키 
{
            "post_id": post["user"]
            + "-"
            + datetime.fromtimestamp(float(post["ts"])).strftime(
                "%Y-%m-%d-%H-%M-%S-%f"
            ),	
}

# 쓰레드에서 게시글 키
{
            "post_id": parent_user_id
            + "-"
            + datetime.fromtimestamp(float(thread["thread_ts"])).strftime(
                "%Y-%m-%d-%H-%M-%S-%f"
            ),
}

- 여튼 이렇게 하면 user + ts = thread_ts + parent를 게시글의 키 값으로 활용하여. 댓글이 어떤 게시글의 댓글인지 확인할 수 있음.

- 근데, 여기서 끝이 아님.. 간혹가다 쓰레드에서 parent_user_id가 남지 않는 경우가 있음 ㅜ. 채널로 전송하기를 체크했을 때 이런 경우가 발생함.

- 위와 같은 방식으로 게시글에 댓글을 달았을 때는 parent_user_id가 남지 않음. 이 때는 아래와 같이 하면됨.

parent_user_id = (
    thread["parent_user_id"]
    if "parent_user_id" in thread.keys()
    else thread["root"]["user"]
)

- root유저가 누구인지 남겨준다. 이 ['root']['user']가 parent_user_id와 동일함.


- 여기까지.. 기본적인 구조에 대해서 설명하였음. 상세 코드에서는 조금 정리 후 다음 글에서 다룰 예정