나는 이렇게 논다/글또 슬랙 봇 개발기

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

daco2020 2024. 10. 24. 12:46
반응형

이전 글: 2024.10.17 - [나는 이렇게 논다/글또 슬랙 봇 개발기] - 슬랙 봇이 보낸 메시지를 수정해봅시다

이전 글에서 슬랙 봇이 보낸 메시지를 수정하는 과정에 대해 설명드렸습니다. 그런데 이는 매우 특수한 상황에 대한 수정 방법이었죠. 아마 똑같이 따라서 구현하는 일은 없을 겁니다.

그래서 이번 글에서는 훨씬 쉬우면서도 모든 슬랙 메시지에 대해 수정할 수 있는 방법을 설명드리겠습니다. (일종의 AS 글인 셈이죠~🤭)
 
먼저, 간단하게 요약하자면 '수정할 메시지를 가져와 고칠 부분만 직접 수정하고 업데이트하는 것'입니다.
 
자 그럼 구현을 시작해 볼까요? (구현은 FastAPI 를 기준으로 설명합니다)
 
 

수정할 메시지 가져오기

냅다 전체 코드부터 꽂고 시작하겠습니다.

@router.get(
    "/messages",
    status_code=status.HTTP_200_OK,
)
async def get_message(
    ts: str,
    channel_id: str,
    type: Literal["message", "reply"] = "message",
    user: SimpleUser = Depends(current_user),
) -> dict[str, Any]:
    if user.user_id not in settings.ADMIN_IDS:
        raise HTTPException(status_code=403, detail="수정 권한이 없습니다.")

    try:
        if type == "message":
            data = await slack_app.client.conversations_history(
                channel=channel_id, latest=ts, inclusive=True, limit=1
            )

        else:
            data = await slack_app.client.conversations_replies(
                channel=channel_id, ts=ts, inclusive=True, limit=1
            )

        message = next((msg for msg in data["messages"] if msg["ts"] == ts), None)
        if not message:
            raise HTTPException(status_code=404, detail="콘텐츠를 찾을 수 없습니다.")

        text = message["text"]
        blocks = message["blocks"]
        attachments = message.get("attachments", [])

        return {
            "text": text,
            "blocks": blocks,
            "attachments": attachments,
        }

    except SlackApiError as e:
        raise HTTPException(status_code=409, detail=str(e))

 
이전 글에서 설명한 것과 마찬가지로 ts, channel_id, user 를 받습니다. 그리고 이번에는 추가로 type 파라미터를 함께 받겠습니다.

type: Literal["message", "reply"] = "message",

 
여기서 type 은 메시지냐, 댓글이냐를 구분하는 역할을 합니다. 이 type 을 기준으로 slack client 의 메서드가 달라집니다.
 
message 타입은 conversations_history 메서드를 사용하고, reply 타입은 conversations_replies 메서드를 사용합니다.

        if type == "message":
            data = await slack_app.client.conversations_history(
                channel=channel_id, latest=ts, inclusive=True, limit=1
            )

        else:
            data = await slack_app.client.conversations_replies(
                channel=channel_id, ts=ts, inclusive=True, limit=1
            )

 
이렇게 구분하는 이유는 댓글 메시지의 경우 conversations_replies 메서드를 사용해야 자신의 정보를 가져올 수 있기 때문입니다.
 
그다음, data["messages"] 내에 ts(메시지의 id 역할) 가 일치하는 message 를 가져오고 우리가 수정을 하는데 필요한 값인 text, blocks, attachments 를 각각 변수에 할당합니다.

        message = next((msg for msg in data["messages"] if msg["ts"] == ts), None)
        if not message:
            raise HTTPException(status_code=404, detail="콘텐츠를 찾을 수 없습니다.")

        text = message["text"]
        blocks = message["blocks"]
        attachments = message.get("attachments", [])

 
이때, attachments 값은 없을 수 있으므로 기본값을 빈배열로 설정해 줍니다.
 
마지막으로 할당한 변수들을 반환하면 수정할 메시지 가져오기는 끝!

        return {
            "text": text,
            "blocks": blocks,
            "attachments": attachments,
        }

 
 
openAPI 로 가져온 결과를 확인해 봅시다.

야무지게 잘 가져왔네요

 
 
 

업데이트 API 추가하기

수정할 값을 가져왔으니 이번에는 업데이트하는 API 를 구현해 보겠습니다.
 
이번에도 전체 코드 먼저 박겠습니다. (가져오기보다 더 짧음)

class UpdateMessageRequest(BaseModel):
    text: str
    blocks: list[dict[str, Any]]
    attachments: list[dict[str, Any]]


async def update_message(
    ts: str,
    channel_id: str,
    data: UpdateMessageRequest,
    user: SimpleUser = Depends(current_user),
):
    if user.user_id not in settings.ADMIN_IDS:
        raise HTTPException(status_code=403, detail="수정 권한이 없습니다.")

    try:
        await slack_app.client.chat_update(
            channel=channel_id,
            ts=ts,
            text=data.text,
            blocks=data.blocks,
            attachments=data.attachments,
        )

        permalink_res = await slack_app.client.chat_getPermalink(
            channel=channel_id,
            message_ts=ts,
        )

        return {"permalink": permalink_res["permalink"]}

    except SlackApiError as e:
        raise HTTPException(status_code=409, detail=str(e))

 
 
우선 UpdateMessageRequest 라고 하는 요청 body 값을 정의해 줍니다. 

class UpdateMessageRequest(BaseModel):
    text: str
    blocks: list[dict[str, Any]]
    attachments: list[dict[str, Any]]

 
이것은 아까 가져온 text, blocks, attachments 와 동일한 형식입니다. 
 
그다음 함수 파라미터에 data: UpdateMessageRequest 처럼 정의해 줍니다.

async def update_message(
    ts: str,
    channel_id: str,
    data: UpdateMessageRequest,
    user: SimpleUser = Depends(current_user),
):

 
 
이렇게 받은 data 를 가지고 slack client 의 chat_update 메서드를 호출해 줍시다.

        await slack_app.client.chat_update(
            channel=channel_id,
            ts=ts,
            text=data.text,
            blocks=data.blocks,
            attachments=data.attachments,
        )

 
 
그다음, 사용자가 빠르게 메시지를 확인할 수 있도록 permalink(수정한 메시지의 url)를 반환해 줍니다. 

        permalink_res = await slack_app.client.chat_getPermalink(
            channel=channel_id,
            message_ts=ts,
        )

        return {"permalink": permalink_res["permalink"]}

 
업데이트는 이게 끝! 참 쉽죠?
 
 
 

고칠 부분만 수정하기

수정할 메시지를 가져오고, 메시지를 업데이트하는 API 까지 만들었습니다.
 
자, 그럼 가져온 값을 어떻게 수정할까요?
 
우선 아까 가져온 수정할 메시지의 값을 복사해 줍니다. openAPI 에서는 아래 화살표로 표시한 버튼을 누르면 값을 복사할 수 있습니다.

 
 
그리고 방금 만든 업데이트 API 의 요청 body 값에 붙여넣기 합니다. (당연히 tschannel_id 도 일치해야 해요😉)

 
 
예시로 구글 url 을 네이버 url 로, '다른 링크로 수정함'은 '더 쉽게 수정함'으로 수정해 보겠습니다.

 
body 내용을 수정했다면, Execute 버튼을 눌러 요청을 보내봅시다.
 
슬랙 채널로 돌아가 확인해 보면 아래처럼 메시지가 바뀐 것을 확인할 수 있습니다.

 
 
 

마무리

이전 글에서는 API 하나로 수정까지 자동화했었습니다. 하지만 이번 글에서는 API 도 분리하고 수정도 직접 해야 하죠. 이렇게 API 를 분리하고 직접 수정하는 것에는 어떤 장점이 있을까요?
 
첫 번째로 모든 메시지에 적용할 수 있습니다. 어떤 '메시지'든 '댓글'이든 가져와 수정을 요청할 수 있죠.
 
두 번째로 더 많은 것을 수정할 수 있습니다. 이 글의 예시처럼 링크만 수정할 수도 있지만, 그 외에 메시지에 담겨져 있는 다른 요소들도 수정할 수 있습니다. 예를 들어, attachments 의 값이 기존에 없었다면 임의로 값을 추가할 수도 있겠죠.
 
세 번째로 버그가 적고 쉽게 대응할 수 있습니다. 메시지를 직접 수정하지 않고 코드로 수정한다면, 경우에 따라 코드가 의도대로 동작하지 않을 수 있습니다.(엣지케이스) 이를 고치려면 코드 자체를 수정하여 서버를 다시 배포해야 하죠. 이와 달리, 내용을 사용자가 직접 수정하면 잘못된 결과가 나오더라도 즉각 다시 수정할 수 있습니다. 



이번 글에서는 슬랙 봇 서버를 이용해 슬랙 메시지를 '더 쉽게' 수정하는 방법을 알아보았습니다. 직접 구현해보면서 자신의 상황에 맞게 커스터마이즈 해보시기 바라겠습니다. 🤗

반응형