코드가 잘못되었다면 기계를 탓하도록 하자.
최초 작성일 : 24.05.31
최종 수정일 : 24.10.18
수정 내역
- 코드 세부 내용 수정 및 작동 확인 완료(24.10.18)
작성자 : 맹알 (sniwoo98@naver.com)
참고 자료
https://brunch.co.kr/@cocosociety/7
https://brunch.co.kr/@cocosociety/9
https://brunch.co.kr/@cocosociety/10
1. 구글 스프레드 시트
1.1 구글 스프레드 시트 열기
https://workspace.google.com/intl/ko/products/sheets/
우상단의 로그인을 누른 뒤, 구글 아이디로 접속 후
제일 왼쪽의 빈 스프레드 시트를 만들도록 하자.
1.2 스프레드 시트 뼈대 작성
시트 기준으로 A열은 이름, C열에 슬랙에 등록된 이메일을 입력하자.
E열의 정보를 확인해서 AppScript가 출석체크 리마인드 메세지를 보내고
F열에 답변 내용을 기록한다.
A열부터 1이니, 원하는 열이 있다면 그에 맞는 숫자로 아래 템플릿을 수정하자.
2. AppsScript 설정
2.1 AppsScript 들어가기
출석정보를 받아오는 시트를 연 뒤, 확장 프로그램 - Apps Script 로 앱 스크립트를 연다.
2.2 템플릿 삽입
아래 자동화 코드 템플릿을 넣어주도록 하자.
- 자동화 코드 템플릿
const mainSheet = SpreadsheetApp.getActiveSpreadsheet(); ///////////// 데이터 정보를 바꾸려면 여기만 수정하세요/////////////////////////////// // 사용하려는 시트의 이름을 입력해주세요. const formSheet = mainSheet.getSheetByName('시트1'); // 봇 유저 토큰 정보 var bot_user_token = ""; // 이름이 기록되는 열 번호 var name_column = 1; // 이메일이 기록되는 열 번호 var email_column = 3; // 출석 정보가 반영되는 열 번호 var attendance_status_column = 5; // 유저의 답변이 저장되는 열 번호 var answer_column = 6; // redash에서 받아오는 인원의 수보다 더 많은 값을 입력해주세요. var student_amount = 10; // 개강 일자 var startDate = new Date("2024-10-15"); // 출석체크 하러가기를 눌렀을 때 나오는 페이지 링크 // "https://"가 포함되어야 합니다. var attend_url = "https://www.naver.com"; /////////////////////////////////////////////////////////////////////// // 오늘 날짜 구하는 함수로 변환 var currentDate = new Date(); var timeDiff = currentDate.getTime() - startDate.getTime(); var weeksPassed = 1 + Math.floor(timeDiff / (1000 * 60 * 60 * 24 * 7)); var daysPassed = 1 + Math.floor(timeDiff / (1000 * 60 * 60 * 24)); function settingMessage() { var name = ""; // 이름 변수 선언 var email = ""; // 이메일 변수 선언 for (var i = 1; i <= student_amount; i++) { name = formSheet.getRange(i, name_column).getValue(); email = formSheet.getRange(i, email_column).getValue(); if (formSheet.getRange(i, attendance_status_column).isBlank()) { var slackID = getSlackIDByEmail(email); // 이메일로 Slack ID 가져오기 if (slackID) { sendMessage(name, slackID); } } } } function getSlackIDByEmail(email) { var url = "https://slack.com/api/users.lookupByEmail?email=" + encodeURIComponent(email); var options = { "method": "get", "headers": { "Authorization": "Bearer " + bot_user_token, "Content-Type": "application/json; charset=utf-8" } }; try { let response = UrlFetchApp.fetch(url, options); var data = JSON.parse(response.getContentText()); if (data.ok) { return data.user.id; } else { console.log("Failed to get Slack ID for email: " + email); return null; } } catch (err) { console.log("Error fetching Slack ID for email: " + email + " - " + err); return null; } } function sendMessage(name, slackID) { console.log("remindMessageSend 진입"); var payload = { channel: slackID, blocks: [ { "type": "header", "text": { "type": "plain_text", "text": "출석체크를 해주세요!", "emoji": true } }, { "type": "section", "text": { "type": "mrkdwn", "text": "안녕하세요 " + name + "님! 즐거운 " + weeksPassed + "주차 입니다! \n출석체크를 잊으셨다면 오른쪽 버튼을 눌러주세요!" }, "accessory": { "type": "button", "text": { "type": "plain_text", "text": "출석체크 하러가기", "emoji": true }, "value": "attend_link", "url": attend_url, "action_id": "button-action" } }, { "type": "divider" }, { "type": "section", "text": { "type": "plain_text", "text": "오늘 모임에 늦거나 참석이 힘드신가요?" }, "accessory": { "type": "radio_buttons", "options": [ { "text": { "type": "plain_text", "text": "참석이 가능해요.", "emoji": true }, "value": "value-1" }, { "text": { "type": "plain_text", "text": "오늘 늦을 것 같아요.", "emoji": true }, "value": "value-2" }, { "text": { "type": "plain_text", "text": "오늘 참석이 어려워요.", "emoji": true }, "value": "value-3" } ], "action_id": "answer_1_value" } }, { "type": "input", "element": { "type": "plain_text_input", "action_id": "answer_2_value" }, "label": { "type": "plain_text", "text": "늦거나 참여가 어려우시면 사유가 어떻게 되시나요? ", "emoji": true } }, { "type": "actions", "elements": [ { "type": "button", "text": { "type": "plain_text", "text": "확인", "emoji": true }, "value": "final_submit", "action_id": "final_submit" } ] } ], text: "[알림] 출석체크 리마인드 메세지" }; var options = { "method": "post", "headers": { "Authorization": "Bearer " + bot_user_token, "Content-Type": "application/json; charset=utf-8" }, "payload": JSON.stringify(payload) }; try { let response = UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", options); var data = JSON.parse(response.getContentText()); console.log(data); } catch (err) { console.log("Error sending message to Slack ID: " + slackID + " - " + err); } } // 슬랙봇에서 무언가 액션이 있을 때 작동하는 함수 doPost(e) function doPost(e) { var parameter = e.parameter; var data = parameter.payload; var json = JSON.parse(data); if (json.actions[0].action_id == "final_submit") { var answerID = json.user.id; // 답변한 사람의 id를 변수에 저장 var answer_1 = json.state.values[Object.keys(json.state.values)[0]].answer_1_value.selected_option.text.text; var answer_2 = json.state.values[Object.keys(json.state.values)[1]].answer_2_value.value; var channel = json.channel.id; var ts = json.message.ts; var email = getEmailBySlackID(answerID); if (email) { writeResponse(email, answer_1, answer_2); messageChangeResult(channel, ts); } else { console.log("Failed to get email for Slack ID: " + answerID); } } } function getEmailBySlackID(slackID) { var url = "https://slack.com/api/users.info?user=" + slackID; var options = { "method": "get", "headers": { "Authorization": "Bearer " + bot_user_token, "Content-Type": "application/json; charset=utf-8" } }; try { let response = UrlFetchApp.fetch(url, options); var data = JSON.parse(response.getContentText()); if (data.ok) { return data.user.profile.email; } else { console.log("Failed to get user info for Slack ID: " + slackID); return null; } } catch (err) { console.log("Error fetching user info for Slack ID: " + slackID + " - " + err); return null; } } // 사용자의 답변을 해당하는 이메일과 매칭하여 시트에 기록해주는 함수 function writeResponse(email, answer_1, answer_2) { console.log("writeResponse 진입"); for (var i = 1; i <= formSheet.getLastRow(); i++) { // 이거였네 찾았다~~~ if (formSheet.getRange(i, email_column).getValue() == email) { // if (answer_1 == "참석이 가능해요.") { answer_1 = "참석"; } if (answer_1 == "오늘 늦을 것 같아요.") { answer_1 = "지각"; } if (answer_1 == "오늘 참석이 어려워요.") { answer_1 = "불참"; } formSheet.getRange(i, answer_column).setValue(answer_1); formSheet.getRange(i, answer_column).setNote(answer_2); } } } // 제출 완료 버튼을 누르면 제출 완료 메세지로 바꿔주는 함수 function messageChangeResult(channel, ts) { var payload = { channel: channel, ts: ts, blocks: [ { "type": "divider" }, { "type": "section", "text": { "type": "plain_text", "text": "응답이 제출되었습니다. 소중한 의견 감사합니다.", "emoji": true } }, { "type": "divider" } ] }; var options = { "method": "post", "headers": { "Authorization": "Bearer " + bot_user_token, "Content-Type": "application/json; charset=utf-8" }, "payload": JSON.stringify(payload) }; UrlFetchApp.fetch("https://slack.com/api/chat.update", options); }
아래 코드 부분은 따로 손 댈 필요 없이,
봇 토큰 정보는 아직 모르니 비워두고 시트의 이름, 열 정보, 인원수, 개강 일자 를 입력하도록 하자.
여기까지 왔다면 앱 스크립트에서 할 일은 거의 끝났다.
2.3 날짜 바꾸기
현재 질문은 ~주차로 표시되는데, 매일 발송하고 싶다면, sendMessage에 weeksPassed 대신 daysPassed 로 교체하도록 하자.
그러면 주차 대신 개강일자 기준 날짜가 계산된다.
그에 맞게 보낼 문구도 ~주차 가 아닌 ~일차 로 자유롭게 바꾸도록 하자.
3. 슬랙 봇 생성 및 설정
3.1 슬랙봇 생성
이제 슬랙 봇을 만들어보자
슬랙 api 사이트에 들어가서 로그인 한 뒤, 우상단의 Your apps로 들어간다.
우상단의 Create New App 클릭 From scratch 선택
앱 이름과 이 앱을 사용할 슬랙 워크스페이스를 선택해준다.
⚠️ 주의 ) 워크스페이스에 권한이 있어야 선택해서 앱을 추가할 수 있다. 게스트로 추가된 워크스페이스라면 권한을 추가하거나, 권한 있는 계정으로 작업을 하도록 하자.
앱 생성이 끝났다. 앱 설정만 하면 끝이다. 거의 다 왔다.
3.2 슬랙 봇 권한 설정
Scope를 먼저 할당하라고 나와있다.
아래 초록 버튼 Review Scopes to add를 누른다.
만약에 해당 버튼이 보이지 않는다면
좌측 메뉴 - Features - OAuth & Permissions 를 통해 들어가도록 하자.
아래로 내리면 Scopes가 있다. Bot Token Scopes 에 권한을 추가한다.
위 네 개 권한을 추가해준다.
3.3 봇 이름 및 프로필 설정
좌측 메뉴 - Features - App Home
아래로 내리면 Your App’s Presence in Slack 이 있다.
슬랙에서 이 봇이 보이는 이름을 설정 해준다.
처음 항목은 마음대로 적어도 되지만, 두번째 항목은 꼭 영어로 적어야한다.
⚠️ 주의 ) 두 번째 항목은 꼭 영어로 적어야 함.
좌측 메뉴 - Settings - Basic Information에서 아래로 스크롤
꾸미기 전
꾸민 후
꾸며줄 수 있다. 센스를 발휘하여 꾸며주도록 하자.
3.4 슬랙 워크스페이스에 슬랙 봇 설치
좌측 메뉴 - Settings - Install App
Install to Workspace 버튼을 통해 워크스페이스에 봇을 설치하도록 하자.
허용 버튼을 누르면 봇이 슬랙 워크스페이스에 성공적으로 설치된다.
⚠️ 주의 ) 만약에 워크스페이스에 권한이 없다면 여기서 앱이 설치되지 않는다. 권한이 있는 계정으로 앱을 설치하도록 하자.
⚠️주의2 ) 슬랙 무료판에는 앱을 최대 10개만 설치할 수 있다고 하니, 사용하지 않는 앱은 삭제하도록하자.
설치가 완료되었다면, 다음과 같은 화면이 뜰 것이다. 여기서 만들어진 봇 토큰을 처음에 App Script에 봇 토큰 입력하는 부분에 넣도록 하자.
⚠️ 주의 ) 여기서 사용하는 봇 토큰은 외부로 유출되면 안된다. 유출에 조심하도록 하자.
슬랙 앱 설정도 거의 끝났다. 이제 실행할 시간이다.
4. 배포 및 실행
4.1 Apps Script 배포
처음에 비워두었던 봇 유저 토큰 정보를 입력해준다.
우상단 배포 버튼 - 새 배포 클릭
좌상단 유형 선택 톱니바퀴 - 웹 앱 선택
액세스 권한이 있는 사용자 - 모든 사용자로 변경
배포 버튼을 누르도록 하자.
액세스 받기.
구글에서는 내가 만든 앱이 위험하다고 한다. (진짜 위험한지는 모르겠다.)
고급 - 제목 없는 프로젝트(으)로 이동 을 누르자. (본인은 책임 안 진다.)
허용 버튼
여기서 나오는 웹 앱 - URL 이 필요하다 복사하도록 하자.
4.2 슬랙봇과 연결
Slack api 좌측 메뉴 - Features - Interactivity & Shortcuts
Interactivity를 켜준다.
켜 주면 위와 같이 URL 입력할 수 있는 공간이 있다. 여기에 방금 복사한 URL을 입력하자.
우하단 Save Changes 버튼 잊지 말자.
⚠️ 주의 ) Apps Script에 변경사항이 생기면 다시 배포하고, 새로 생긴 URL을 입력해줘야 한다.
4.3 Apps Script 실행
좌상단의 실행 버튼을 누른다.
5. 결과
확인을 누르면 응답이 제출된다.
6. 자동 발송
현재는 실행 버튼을 누르면 각 행 데이터를 돌면서 발송하는데, 매 주(매일) 보내려고 하면 AppS Script 좌측 메뉴에 트리거를 이용하도록 하자.
우하단 트리거 추가 클릭
이벤트 소스 선택 - 시간 기반
일 단위로 보내길 원하면, 일 단위 타이머, 주 단위로 보내고 싶다면, 주 단위 타이머
이제 매일 오전 9시~10시 사이에 실행 될 것이다.
7. 유지보수
⚠️ Apps Script 코드 내에 변경이 있을 경우 새로 배포해서 슬랙 api에 Interactivity에 새로 적용해주어야한다.
8. 추가기능
설문조사
https://brunch.co.kr/@cocosociety/10
블록킷 빌더
https://app.slack.com/workspace-signin?redir=%2Fgantry%2Fblock-kit-builder
블록 킷 빌더를 통해서 메세지를 간편하게 만들 수 있다.
왼쪽에서 원하는 내용을 넣으면 오른쪽에 코드가 만들어진다.
Apps Script내의 payload의 blocks에 코드를 복사해서 넣으면 보낼 수 있다.
** 궁금한 점 or 오류 있으시면 오류는 해당 오류 메세지 복사해서 댓글로 달아주시면 확인 후 답변 드리겠습니다.
'이것 저것' 카테고리의 다른 글
월매출 3000을 만들라고요? 우당탕탕 프로젝트 결과보고 (0) | 2024.11.04 |
---|---|
기업이 경력없는 당신을 뽑을 이유가 뭔데? (2) | 2024.10.11 |
개발자 공부를 하던 내가 신입 APM? (2) | 2024.10.09 |
성장에 미친 사람들이 나에게 주었던 영향 (0) | 2024.09.27 |
머리 꽃밭, 극 NF의 프리모템(Pre-mortem) 경험기 (0) | 2024.09.10 |