소개
저는 현재 IT 서비스 회사에서 일하고 있습니다. 유저가 특정 행동을 할 때마다 슬랙 메시지가 오게끔 되어 있습니다.
특정 행동을 하면 -> '시작' 내용의 스레드가 생기고 -> 그 아래 댓글이 달리는데, 댓글의 형태는 코드박스+JSON 입니다. 그 JSON 코드 안에는 '성공' 또는 '실패' 라는 단어가 들어 있습니다. 성공 실패 여부를 확인하려면 스레드 댓글보기를 누르고 댓글을 일일이 확인해야 합니다.
이렇게 특정 행동을 하면 스레드가 생기고
이 아래에 댓글이 달립니다
저는 '실패' 댓글이 달린 스레드만 매일 아침 자동으로 구글 드라이브 시트에 쌓이기를 원합니다.
진행 방법
칼퇴 자동화 스터디에선 클로드를 쓰라 하셨으나... 지금 GPT 유료로 사용하고 있어 돈을 더 쓰기가 싫어서용 ;ㅅ; 일단 저의 친구 GPT-4o 와 함께해 보았습니다.
이하 음슴체로 쓰겠습니다.
Step1) 프롬프트 넣기
첫 번째 프롬프트 :저는 [직업/하는 일]을 하고 있는데, 반복되는 업무를 자동화하고 싶어요. 1. 제가 매일 힘들어하는 일은: 슬랙에서 댓글에 '실패'라는 단어가 있는 스레드를 엑셀 시트로 수집하는 일이에요. 한 스레드에는 { "unitCode": "---", "siteCode": "---", "defaultSite": "---", , "code": "---", "campaignId": "---", "message": "캠페인 그룹이 제출되었습니다." } 이런 정보가 들어 있고, 이렇게 생긴 스레드의 댓글에 { "campaignGroupCode": "---", "unitCode": "---", "siteCode": "---", "message": "계정 동기화 단계에서 실패", "errMessage": "Request failed with status code 400" } 이렇게 '실패'라는 단어가 들어있으면 스레드의 unitCode, 실패가 들어간 문장을 수집해서 엑셀 시트에 자동으로 들어갔으면 해요.
하지만 이렇게 요청했더니 '스레드' 정보만 읽어오고 '댓글' 정보는 읽어오지 않음. 그래서 나중에 프롬프트를 수정해야 했음.
Step2) 실행 전 준비 - 슬랙 access token, 스크랩 할 슬랙 채널 ID, 구글 드라이브 시트 ID
GPT는 Slack API 설정을 하고, OAuth & Permissions 에서 접근 권한을 받아오라 함. 하나씩 차근차근 알려줘서 access token 을 받아오는 건 어렵지 않았음. 그리고 슬랙 채널 ID도 GPT가 알려준대로 해서 잘 받아옴
Step3) 자동화 코드 스크립트 실행
1트 : 실패 - 아래의 에러 발생
실패 이유 : TypeError: Cannot read properties of undefined (reading 'forEach')
Next Step : 더 구체적으로 결과값을 보기 위해 '어제' 생성된 스레드를 긁어오라고 구체적으로 지시
2트 : 실패 - 생성된 메시지 없음 (없을리가 없는데)
실패 이유 : 모르겠어ㅠㅠ gpt가 발생 가능한 원인을 8개나 알려줌...
Next step : 한참을 삽질하다가 8개 원인 하나하나 소거함
1) 잘못된 채널id -> 잘 들어가 있어
2) slack 권한(scope) 없음 -> 너가 말한 conversations.history 는 이제 존재하지 않아. 나머지 --.history 권한은 모두 추가했어
3) slack 에 앱이 초대되지 않음 -> 얘 인가 보다! 앱 초대는 어떻게 해? -> 슬랙에서 invite @앱이름 으로 해결!
4) 비공개 채널 -> 공개 채널이야
5) slack api 호출 url 잘못됨 -> 모르겠어
6) API rate limit 초과 -> 아닐걸
7) slack api의 ok:false 응답 확인 -> ok:false 나왔어
8) 채널에 메시지가 없음 -> 아냐 메시지는 많아
3트 : 실패 - 스레드 처리 중 에러 발생
실패 이유 : 스프레드 시트의 ID가 잘못 들어감
Next step : 스프레드 시트를 새로 판 다음 링크 URL을 다 주고 알아서 넣으라고 함
4트 : 반쯤 실패 - 실행은 완료 BUT, 데이터가 이상해
결과 : 실행은 완료 되었으나, N/A N/A N/A N/A N/A N/A 2024-11-12T00:53:15.000Z 2024-11-13T14:06:24.198Z 이렇게 나옴
실패 이유 : 모르겠어...
Next Step : 그냥 실패했다고 알려줬더니 코드 새로 짜줌
5트, 6트 : 실패 - 새로운 에러
결과 :TypeError: Cannot read properties of undefined (reading 'ts') processThread @ Code.gs:2 / 유효하지 않은 스레드 데이터: undefined /
7트 : 반쯤 실패 - 실행은 완료 BUT, 여전히 데이터가 N/A로 나옴
Next Step : 아무래도 json 형태인 것 같으니까, 일단은 스레드의 값을 엑셀 A열로 통째로 가져와. 그러고 난 다음에 해당 셀의 Json 을 파싱 해서 내가 원하는 값을 줘.
8트 : 반의 반쯤 실패 - 로그는 뽑았는데 원하는 조건이 아님
결과 : 계속 뚱땅 거리다 보니 어떤 값을 뽑아냄. BUT, 그 값은 코드박스에 들어 있는 '실패'라는 단어가 아니라 내가 슬랙 댓글로 달아둔 '실패' 를 추출해 옴. 하지만 자동화 하고 싶은 건 코드박스에 있는 '실패'라는 단어가 들어 있으면 그 스레드를 가져오는 것임.
Next Step : json 내 '실패'라는 단어를 못 가져오는 것 같아서 실행로그를 보면서 어떤 값을 가져오는지 확인하려 함
실행로그를 보면 저렇게 '변경합니다' '생성합니다' 같은 한글을 추출해야 하는데, 그걸 못하고 있음...
작업시간은 5시간 정도였는데 아직 성공을 못함....ㅠㅠㅠ.... (도움이 필요해요)
결과와 배운 점
1. 배운 점
1) gpt한테 에러만 띡 보여주면 gpt도 삽질할 가능성이 높음 -> 에러의 원인을 사람이 파악하려는 작업이 필요함
gpt가 에러 원인 가능성 8개를 줬을 때 '아 이중에 하나만 알려줘!'라고 짜증을 냈는데, 생각해보니 gpt도 모르잖아... 그래서 하나씩 확인 후 답을 줬더니 해결될 수 있었음.
2) 코드를 새로 넣을 때 꼭 한번씩 함수가 없다는 등 1줄 짜리 에러가 남
저는 싹 [다 지우고 + 저장 + 새로운 코드 넣기] 로 하고 있음
3) gpt한테 로그 전문을 다 줬더니 더 잘 이해하.....는 척 함... 아직 해결은 안됨 ㅠㅠ
2. 도움이 필요해요
1) 슬랙 댓글 중 '코드박스' 형태의 댓글을 읽어오게 하려면 어떻게 해야 할지...
2) (느낌상) 댓글을 다 안 긁어오는 것 같은데... 보다 구체적으로 '모든 댓글을 다 긁어와'라고 프 롬프트를 써야 할지...
3. 앞으로의 계획 : 될 때까지 해보려구요 ^.ㅠ ...
--가장 마지막 코드 블록--
function collectThreadsByKeywordFromCodeBox() {
const token = ''; // Slack API 토큰
const channelId = ''; // Slack 채널 ID
const spreadsheet = SpreadsheetApp.openById(''); // 스프레드시트 ID
const startDate = new Date('2024-11-11'); // 검색 시작 날짜
const endDate = new Date('2024-11-14'); // 검색 종료 날짜
const startTimestamp = Math.floor(startDate.setHours(0, 0, 0, 0) / 1000); // 시작 유닉스 타임스탬프
const endTimestamp = Math.floor(endDate.setHours(23, 59, 59, 999) / 1000); // 종료 유닉스 타임스탬프
let sheet = spreadsheet.getSheetByName('Slack Data');
if (!sheet) {
Logger.log('Slack Data 시트를 찾을 수 없어 새로 생성합니다.');
sheet = spreadsheet.insertSheet('Slack Data');
sheet.appendRow(['json']); // A열에 'json' 필드 설정
}
const historyUrl = `https://slack.com/api/conversations.history?channel=${channelId}&inclusive=true&oldest=${startTimestamp}&latest=${endTimestamp}`;
const payload = {
method: 'get',
headers: { 'Authorization': 'Bearer ' + token },
};
try {
Logger.log("Slack API 호출 시작");
const response = UrlFetchApp.fetch(historyUrl, payload);
const data = JSON.parse(response.getContentText());
Logger.log(`Slack API 응답: ${JSON.stringify(data)}`);
if (data.ok && Array.isArray(data.messages) && data.messages.length > 0) {
data.messages.forEach((thread) => {
if (!thread.ts) return;
const threadUrl = `https://slack.com/api/conversations.replies?channel=${channelId}&ts=${thread.ts}`;
const threadPayload = {
method: 'get',
headers: { 'Authorization': 'Bearer ' + token },
};
const threadResponse = UrlFetchApp.fetch(threadUrl, threadPayload);
const threadData = JSON.parse(threadResponse.getContentText());
Logger.log(`스레드 응답 데이터: ${JSON.stringify(threadData)}`);
if (threadData.ok && threadData.messages) {
threadData.messages.forEach((reply) => {
Logger.log(`댓글 텍스트: ${reply.text}`);
// 코드박스(````{}```) 내부 텍스트 추출
const codeBlockMatch = reply.text.match(/```({[\s\S]*?})```/); // 정규식으로 코드박스 탐지
if (codeBlockMatch) {
try {
const parsedJSON = JSON.parse(codeBlockMatch[1]); // 코드박스 내부 JSON 파싱
Logger.log(`코드박스에서 파싱된 JSON 데이터: ${JSON.stringify(parsedJSON)}`);
// JSON 데이터에서 키워드 검색
const jsonString = JSON.stringify(parsedJSON); // JSON 전체를 문자열로 변환
if (
jsonString.includes('실패') ||
jsonString.includes('fail') ||
jsonString.includes('failed') ||
jsonString.includes('error') ||
jsonString.includes('성공')
) {
Logger.log(`조건에 맞는 스레드: ${JSON.stringify(thread)}`);
sheet.appendRow([JSON.stringify(thread)]); // A열에 JSON 데이터 저장
}
} catch (e) {
Logger.log(`코드박스 JSON 파싱 오류: ${e.message}`);
}
} else {
Logger.log('댓글에서 코드박스를 찾을 수 없습니다.');
}
});
}
});
} else {
Logger.log("Slack API에서 가져온 messages 데이터가 비어 있습니다.");
}
} catch (error) {
Logger.log("에러 발생: " + error.message);
}
}
도움 받은 글 (옵션)
아직은 없습니다!