구글 캘린더 - 휴가자 상태 변경 작업 이후 들어온 추가 요청.
5개월만에 착수.
1. 슬랙 내 모든 메시지에 훅을 걸 수 있어야한다.
2. 메시지에 태그가 있는지 확인한다.
3. 태그 걸린 유저가 휴가중인지 확인한다.
4. 태그 작성자에게 휴가자 멘션 알림을 보내준다.
슬랙 앱 설정 내 Event Subscriptions 에서 message.channels 이벤트 구독을 추가한다.
Subscribe to bot events vs Subscribe to events on behalf of users
왼쪽은 bot이 직접 구독하는 것이기 때문에 나의 경우 메시지를 구독하고 싶은 채널마다 app을 추가해줘야한다. 반면 오른쪽은 유저를 대신하여 즉, 유저가 메시지를 볼 수 있으면 앱에도 메시지가 구독된다. 왼쪽은 휴가자 멘션 알림을 bot이 해줄수 있지만, 오른쪽은 유저가 보낸것처럼 된다.
각 장단점이 있지만 채널마다 app 추가하는것이 너무 귀찮아서 오른쪽 선택.
이벤트를 구독하면 해당 이벤트가 발생할때마다 post 할 api 주소를 등록해야 한다.
/mention/callback
을 등록했는데 특이하게 인증을 받으려면 challenge라는 reponse 값을 뱉어줘야한다.
메시지에 태그가 있는지 확인
const regex = /<@([A-Z0-9]+)>/g;
const matches = [];
let match;
while ((match = regex.exec(text)) !== null) {
// 반복문을 도는 이유는 태그가 여러개일수도 있어서...
const userId = match[1];
matches.push(userId);
}
if (matches.length) {
matches.forEach(async (uid) => {
// 태그 걸린 사람의 user profile 가져와서 status_text 확인
// 휴가중인 사람은 '휴가중'
const { data: vacationerData } = await axios.get(`https://slack.com/api/users.profile.get?user=${uid}&pretty=1`, {
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${slackToken}`,
},
});
if (!vacationerData.ok) return;
const vacationerRealName = vacationerData.profile.real_name;
const statusExp = vacationerData.profile.status_expiration;
const statusText = vacationerData.profile.status_text;
const time = new Date().getTime();
});
}
if (statusText === '휴가중' && time < (req.body.event_time + 4) * 1000) {
// 작성자의 token을 db에서 가져오기 위해 작성자 user profile 가져오기
const { data: writerData } = await axios.get(`https://slack.com/api/users.profile.get?user=${writerId}&pretty=1`, {
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${slackToken}`,
},
});
if (!writerData.ok) return;
const writerName = writerData.profile.real_name;
const DBUser = await getDBUser(writerName);
if (!DBUser.token) return;
console.log(uid, vacationerRealName, time, req.body);
// 작성자인척 휴가자 멘션 알림 보내기
const slackBot = new WebClient(decrypt(DBUser.token));
slackBot.chat.postMessage({
channel,
thread_ts: threadTs,
text:
statusExp > 0
? `${vacationerRealName}님은 ${format(new Date(statusExp * 1000), 'M/d H')}시까지 휴가입니다 :palm_tree:`
: `${vacationerRealName}님은 휴가중입니다 :palm_tree:`,
});
}
요상한 점
이상하게 메시지 발생 한번에 hook이 두번~네번 발생함. 이유를 도저히 모르겠음.
위에 대한 대응
matches.forEach(async (uid) => {
...
if (statusText === '휴가중' && time < (req.body.event_time + 4) * 1000) {
...
}
}