슬랙 메시지를 구글 시트로 옮기기 (2) - 성공!

목적 | 슬랙 메세지 중 특정 조건을 달성하는 메시지를 구글 스프레드 시트에 매일 오전 9시 쌓이도록 자동화 하기

진행 과정 |

첫 번째 5시간 시도했을 때 깨달은 건 "gpt는 한번에 해달라고 하면 과부화가 오는구나!"
--> gpt에게 step by step 으로 차근차근 하도록 유도함

첫 번째 시도했을 때 계속 발생했던 오류
1) 스레드와 댓글의 차이를 이해하지 못하고 '스레드'만 가져옴
2) 슬랙의 텍스트가 codebox 안에 들어있는데, text 형식만 가져옴
3) json 코드를 가져오는 건 잘 하지만, 그 안에 있는 값 (예 : unitcode:"~~") 를 엑셀 셀에 넣으라고 하면 실패함

개선 방향
1) 스레드와 댓글의 차이를 예시로 보여줌 -> GPT가 알아서 parent message, child message라고 명칭을 붙이며 이해함
2) 처음부터 JSON형식으로 된 codebox안에 있는 '실패'라는 단어를 가져오겠다고 지시함
3) json 을 A열에 통으로 가져오고, A열에서 하나씩 쪼개라고 지시함


1단계 : 맨 처음에 "대답하지 말고 내 말 들어"

일단 기존 chat을 계속 활용하니까 애가 과부화가 온 것 같아서 새로운 chat을 열음.
새로운 chat에서 배경과 목적을 설명하자 바로 코드를 짜주려 함. 일단 멈추게 하고 하나씩 예시를 들어 설명함.

1) 역할 규정하기 :

나는 1. 매일 오전 9시에 2. 특정 조건을 달성하는 슬랙 메시지를 3. 구글 스프레드 시트에 자동으로 쌓는 자동화를 최종 만들 거야. 그런데 바로 이렇게 하면 어려움이 많아서 단계별로 차근차근 할 거야. 너는 이제 시니어 개발자가 되어 날 도와줘야 해 알겠니?

2) 조건 구체적으로 설명하기 :

나는 슬랙에서 ###라는 채널의 스레드+댓글을 가져와서 그 안에 "실패" 라는 단어가 있는지 확인해야 해. 스레드와 댓글은 일반 텍스트, json 코드 박스로 이루어져 있어. 어떤 스레드에는 댓글이 최대 35개~50까지 달릴 수 있어. 나는 그 댓글들 중 json 코드박스로 구성되어 있으며 그 코드박스 안에 '실패' 라는 단어가 들어 있는지를 확인하고, 만약 들어 있다면 그 댓글의 스레드 정보를 구글 드라이브 시트에 누적해서 관리하고 싶어. 예시를 들어 줄게 잠깐 기다려볼래? (예시 제공)

3) 특정 날짜로 지정하기 :

일단 테스트를 위해 오늘 날짜로만 코드를 만들어줘.


2단계 : '실패' 라는 단어가 들어 있는 스레드 우선 가져와서 형식 확인하기

1) '실패'라는 단어가 들어 있는 스레드를 호출하여 가져왔을 때 로그 확인하기

나는 조건에 맞는 스레드 하나를 준비해뒀어. 부모 메시지 ID의 타임스탬프는 ####야. 메시지는 코드블록 형태로 되어 있어.

2) 로그 공유하기 문제 원인 파악하기

자 우선 해당 스레드의 모든 텍스트를 긁어온 로그를 보여줄게. 기다려봐.

--> Slack API에서 로그가 너무 길어 잘리지 않고도 필요한 메시지 데이터를 효율적으로 추출하려면, JSON 데이터를 분석하고 원하는 텍스트 정보를 추출하는 코드가 필요합니다."실패"라는 단어가 포함된 메시지만 필터링하여 출력하려면 Slack API의 응답에서 attachmentsblocks 필드 안의 text 또는 message 값을 파싱해야 합니다. 이를 위해 아래와 같이 코드를 작성할 수 있습니다.

주어진 JSON 응답 데이터를 분석한 결과, "message": "실패하였습니다."와 같은 메시지는 attachmentsblockscontextelementsmrkdwn.text 경로에 존재할 가능성이 큽니다. 이를 기반으로 다시 코드를 작성하겠습니다.

3단계 : 원하는 값이 나오고 난 다음에 부가 기능 추가하기

- 구글 스프레드 시트에 자동으로 입력해줘
- 매일 아침 9시에 자동으로 구글 스프레드시트에 입력되게 자동화 시켜줘

결과 : 정상 작동!

중국어 텍스트가 포함된 Google 스프레드시트의 스크린샷

한 가지 아쉬운 점은 'parent message(스레드)'를 가져오고 싶었으나, 그건 계속 시도해도 실패함. 조건을 충족하는 child message를 찾고 -> 그 상위 parent message를 가져오게 하려 하였으나, child-parent 관계는 gpt가 임의로 설정한 관계이어선지 parent message에서 '실패'라는 단어가 들어 있는 스레드를 찾으려 함. 하지만 parent message에는 '실패'라는 단어가 들어있지 않아 실패함. 그래서 그냥 child message만 찾는 방향으로 타협함.

function automateFailureMessagesToSheet() {
  const slackBotToken = "####";
  const channelId = "####";
  const sheetId = "####";
  const keyword = "실패"; // 찾고자 하는 키워드

  const today = new Date();
  const yesterday = new Date(today);
  yesterday.setDate(today.getDate() - 1);

  const startDate = new Date(`${yesterday.getFullYear()}-${yesterday.getMonth() + 1}-${yesterday.getDate()}T00:00:00Z`).getTime() / 1000; // 어제 UTC 시작
  const endDate = new Date(`${yesterday.getFullYear()}-${yesterday.getMonth() + 1}-${yesterday.getDate()}T23:59:59Z`).getTime() / 1000; // 어제 UTC 종료

  let failureMessages = [];
  let parentMessages = [];
  let nextCursor = null;

  // Step 1: 어제 하루치 부모 메시지 가져오기
  do {
    let url = `https://slack.com/api/conversations.history?channel=${channelId}&oldest=${startDate}&latest=${endDate}`;
    if (nextCursor) url += `&cursor=${nextCursor}`;
    const options = {
      method: "get",
      headers: {
        Authorization: `Bearer ${slackBotToken}`
      }
    };

    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());

    if (data.ok) {
      parentMessages = parentMessages.concat(
        data.messages.filter(msg => msg.thread_ts && msg.thread_ts === msg.ts)
      );
      nextCursor = data.response_metadata?.next_cursor || null;
    } else {
      Logger.log(`Error fetching history: ${data.error}`);
      break;
    }
  } while (nextCursor);

  Logger.log(`Found ${parentMessages.length} parent messages for yesterday.`);

  // Step 2: 각 부모 메시지의 스레드 메시지 가져오기
  parentMessages.forEach(parentMessage => {
    const url = `https://slack.com/api/conversations.replies?channel=${channelId}&ts=${parentMessage.ts}`;
    const options = {
      method: "get",
      headers: {
        Authorization: `Bearer ${slackBotToken}`
      }
    };

    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());

    if (data.ok) {
      data.messages.forEach(message => {
        // Step 3: 실패 메시지 필터링 및 파싱
        if (message.attachments) {
          message.attachments.forEach(attachment => {
            if (attachment.blocks) {
              attachment.blocks.forEach(block => {
                if (block.type === "context" && block.elements) {
                  block.elements.forEach(element => {
                    if (element.type === "mrkdwn" && element.text.includes(keyword)) {
                      const rawText = element.text;
                      const parsed = JSON.parse(rawText.replace(/```/g, ""));
                      failureMessages.push({
                        date: new Date(message.ts * 1000).toISOString().split("T")[0], // 메시지의 날짜
                        raw: rawText,
                        unitCode: parsed.unitCode || "",
                        campaignCode: parsed.campaignCode || "",
                        message: parsed.message || "",
                        errMessage: parsed.errMessage || ""
                      });
                    }
                  });
                }
              });
            }
          });
        }
      });
    } else {
      Logger.log(`Error fetching replies for TS ${parentMessage.ts}: ${data.error}`);
    }
  });

  // Step 4: Google Sheet에 저장
  const sheet = SpreadsheetApp.openById(sheetId).getActiveSheet();
  failureMessages.forEach(msg => {
    sheet.appendRow([msg.date, msg.raw, msg.unitCode, msg.campaignCode, msg.message, msg.errMessage]);
  });

  Logger.log(`Saved ${failureMessages.length} failure messages to Google Sheet.`);
}

function createTrigger() {
  ScriptApp.newTrigger("automateFailureMessagesToSheet")
    .timeBased()
    .atHour(9)
    .everyDays(1)
    .create();
}
3
1개의 답글

👉 이 게시글도 읽어보세요