어제 블로그 글을 쓰면서 생각을 정리해보니 내가 뭔가 착각을 하고 있었다는 걸 깨달았다.
제 3자를 경유하려고 한 이유는 일반적인 상황에서 내가 봇과 대화중일 때
봇이 답장을 보내도 봇이 보낸 답장을 알림으로 받지 못하기 때문이다.
봇이 보낸 답장을 알림으로 받지 못하면 스크립트에서 해당 내용을 처리하는 코드가 실행이 안된다.
그런데 듀얼메신저를 사용하면 이야기가 달라진다.
듀얼메신저를 사용하면 내 핸드폰에 카톡이 2개가 깔려있는 셈이된다.
따라서 내가 원래 쓰던 카톡으로 봇과 대화를 하며 메세지를 보내면,
내가 보낸 메세지에 대해 봇카카오톡에서 알림이 뜬다.
그리고 중요한 것은 메신저봇 어플이 봇카카오톡에 대한 알림도
내 원래의 카카오톡 알림과 동일하게 받아들여 인식한다는 것이다.
가령 내 이름이 "김이박" 이라고 하자
만약 내가 봇에게 채팅을 보내면 봇카카오톡에서는 "김이박" 에게 메세지가 온 것으로 인식한다.
그리고 이는 메신저봇에서 그대로 받아들인다.
중요한 것은, 내 원래 카카오톡에서는 친구중에 "김이박"이 있지 않는 이상
절대로 내 자신에게서 온 카톡에 대해 알림을 받을 일이 없다는 것이다.
따라서 만약 보낸사람이 "김이박"인 경우엔 봇카카오톡으로 온 알림이고
그 이외에는 전부 나한테 온 알림으로 처리하면 되기 때문에 코딩도 훨씬 쉬워진다.
이 과정을 통해 공기계 없애 내 폰하나만으로 명령어를 처리하는 과정을 넣었다.
과정을 정리하면 다음과 같다.
봇에게 명령어 전달 - 봇 카카오톡으로 명령어에 대한 알림 뜸 - 알림을 읽어들여 스크립트에서 함수 실행(명령어 처리)
일반적으로는 두번째 과정이 하나의 폰에서 일어날 수가 없기때문에 공기계를 경유해야했지만,
듀얼메신저를 사용했기에 두번째 과정이 하나의 폰에서 일어날 수 있었다.
그렇게 수정한 코드는 다음과 같으며 훨씬 더 간결해졌다.
const scriptName = "test";
const manager = "커맨드봇";
const master = "김이박";
//var
let date;
let msg_send;
let msg_introduce = "안녕하세요! 저는 김이박님의 봇 '버듀(가명)' 입니다." + '\n' + "앞으로 김이박님이 답장하실 수 없을 때는 제가 대신 답장을 하니 너무 놀라지 말아주세요 :)";
let msg_sleep = "지금은 김이박님이 자고 있습니다. 아침에 다시 연락해주세요.";
let msg_study;
let now_year; let now_month; let now_date; let now_day; let now_hour; let now_min; let now_sec; let now_milsec;
let sleep;
let study;
let timer_set = new Object();
let timer_time = 1000*60*30;
let send_delay = 5;
let user_meet = {};
let user_meet_data = DataBase.getDataBase("user_meet").split('\n');
for (var i = 0; i < user_meet_data.length; i++)
{user_meet[user_meet_data[i].trim()] = 1;}
let study_time = {
'0' : {},
'1' : {},
'2' : {},
'3' : {},
'4' : {},
'5' : {},
'6' : {}
};
let send_time = {
'year' : 0,
'month' : 0,
'date' : 0,
'hour' : 0,
'minute': 0,
'second': 0,
'milsec': 0
};
//일정 세팅
set_study(1, 13, 16);
set_study(2, 9, 11);
set_study(2, 14, 17);
set_study(2, 19, 20);
set_study(3, 11, 18);
set_study(3, 19, 20);
set_study(4, 9, 14);
set_study(5, 11, 12);
set_study(6, 12, 13);
function response(room, msg, sender, isGroupChat, replier, imageDB, packageName) {
DataBase.appendDataBase("testDB", msg + '\n');
//auto setting
set_now_time();
set_send_time(now_year, now_month, now_date, now_hour, now_min, now_sec, now_milsec);
set_sleep(0, 9);
if (now_hour in study_time[now_day])
{
study = true;
msg_study = "김이박님은 지금 수업중입니다." + '\n' + study_time[now_day][now_hour] + "시에 수업이 끝나니 그때 연락주세요.";
}
else
{study = false;}
set_reply(msg);
//user setting
//sleep = false;
//DANGEROUS TEST :: 단톡방으로 채팅이 갈 수 있으니 주의!!!
//replier.reply(room + ' ' + sender);
if (sender == master) // 내가 커맨드봇에게 메세지 보낸 경우
{
let msg_recv = msg.split(' ');
if (msg_recv.length >= 2 && msg_recv[1].trim() == "확인")
{
timer_set[msg_recv[0].trim()] = false;
replier.reply("타이머를 해제했습니다.");
var key = Object.keys(timer_set);
var rest = "[타이머 목록]" + '\n';
rest += show_value(timer_set);
replier.reply(rest);
}
else
{
replier.reply("확인하였습니다.");
//replier.reply(show_value(user_meet));
//set_now_time();
//replier.reply(show_value(send_time));
//java.lang.Thread.sleep(1000*5);
//replier.reply(set_now_time());
//replier.reply(now_hour + ' ' + now_min + ' ' + now_sec + ' '+ now_milsec);
//replier.reply(show_value(send_time));
}
}
else if (!isGroupChat && (sender != "친구") && (sender != manager)) //본 계정으로 톡이 온 경우 (단톡방, 친구 제외)
{
if (!(room in timer_set))
{ timer_set[room] = false; }
java.lang.Thread.sleep(5000);
if (set_now_time())
{
if (!(sender in user_meet)) /** 만난적 없는 사람의 경우, 인사말 추가 */
{
DataBase.appendDataBase("user_meet",sender+'\n');
user_meet[sender] = 1;
replier.reply(msg_introduce);
}
//test
//replier.reply(now_day + ' ' + now_hour + ' ' + study );
//test
if (sleep)
{replier.reply(msg_sleep);}
else if (study)
{replier.reply(msg_study);}
else // free now
{
if (!timer_set[room])
{
timer_set[room] = true;
replier.reply(manager,"[notice] " + room + " 에서 " + sender + " 에게 카톡이 왔습니다.");
replier.reply(manager, show_value(timer_set));
//test
//timer_time = 1000*10;
java.lang.Thread.sleep(timer_time);
if (timer_set[room])
{
replier.reply(msg_send);
timer_set[room] = false;
}
}
}
}
}
}
//function
function show_value(obj)
{
var str = "";
var key = Object.keys(obj);
for (var a in key)
{
str += (key[a] + ' : ' + obj[key[a]]);
str += '\n';
}
return str;
}
function set_now_time()
{
date = new Date();
now_year = date.getFullYear();
now_month = date.getMonth();
now_date = date.getDate();
now_hour = date.getHours();
now_min = date.getMinutes();
now_sec = date.getSeconds();
now_day = date.getDay();
now_milsec = date.getMilliseconds();
if (now_year > send_time['year'])
{return true;}
else if (now_month > send_time['month'])
{return true;}
else if (now_date > send_time['date'])
{return true;}
else if (now_hour > send_time['hour'])
{return true;}
else if (now_min > send_time['minute'])
{return true;}
else if (now_sec > send_time['second'])
{return true;}
else
{
if (now_sec < send_time['second'])
{return false;}
else if (now_milsec > send_time['milsec'])
{return true;}
else
{return false;}
}
}
function set_study(day, start, end) {
for (var i = start; i < end; i++)
{study_time[day][i] = end;}
}
function set_sleep(start, end) {
if (start > end)
{
if ((start <= now_hour && now_hour < 24) || (0 <= now_hour && end))
{sleep = true;}
else
{sleep = false;}
}
else
{
if (start <= now_hour && now_hour < end)
{sleep = true;}
else
{sleep = false;}
}
}
function set_reply(recv_msg)
{
msg_send = "일정시간 경과 후 자동응답 테스트 중입니다. :: 메세지를 수신 후 30분 경과했습니다.";// + '\n' + recv_msg + '\n' + date.getSeconds();
}
function set_send_time(year, month, date, hour, min, sec, milsec)
{
sec += send_delay;
if (sec > 59)
{
sec %= 60;
min += 1;
}
if (min > 59)
{
min %= 60;
hour += 1;
}
if (hour > 23)
{
hour %= 24;
date += 1;
}
if (month == 4 || month == 6 || month == 9 || month == 11) // to 30
{
if(date > 30)
{
date = 1;
month += 1;
}
}
else if(month == 2)
{
if ((year%4 == 0 && year%100 != 0) || year%400 == 0) // 윤년이면
{
if (date > 29)
{
date = 1;
month += 1;
}
}
else
{
if (date > 28)
{
date = 1;
month += 1;
}
}
}
else
{
if (date >31)
{
date = 1;
month += 1;
}
}
if (month > 11)
{
month = 0;
year += 1;
}
send_time['year'] = year;
send_time['month'] = month;
send_time['date'] = date;
send_time['hour'] = hour;
send_time['minute'] = min;
send_time['second'] = sec;
send_time['milsec'] = milsec;
}
코드가 좀 길어보이지만, 함수를 뺀 위에서 언급한 내용에대한 코드는 간단하다ㅋㅋ
그리고 위에 말한 내용 말고도 몇가지 기능들을 조금 추가해보았다.
1. 처음으로 대화를 한 사람에게만 첫인사를 한다.
메신저봇에는 DB기능이 있다.
말은 거창하지만 그냥 파일로 읽고 쓰기다 ㅋㅋ
따라서 지금까지 만났던 사람들을 모두 DB에 저장해두고
스크립트가 컴파일되면 DB에 있는 내용을 불러와 user_meet 오브젝트에 저장한다.
어떤 사람에게 톡이 왔을 때 그 사람이 user_meet 에 있다면 인사를 하지 않고
user_meet에 없다면 인사를 하면서 그 사람을 DB와 user_meet에 추가한다.
2. 메세지 송신자가 메세지를 다 보낼 때까지 기다렸다가 답장을 한다.
실제 생활에 봇을 켜놓고보니 생각난 문제점을 해결해보았다.
보통 카톡을 보낼 때 카톡 메세지 하나에 하고 싶은 말을 다 보내는 경우는 거의 없다.
야
뭐해
자냐?
처럼 3번에 나눠서 보내기도 한다.
그런데 만약 내가 자고있거나 수업중일 때 저렇게 카톡이 온다면
야 / 뭐해 / 자냐? 라는 3개의 메세지에 각각 반응하여 자는 중이라는 메세지가 3번 발신된다.
그래서 마지막 메세지를 받은 시간으로부터 5초 후에 메세지에 답장할 수 있도록 했다.
(밀리초까지 나름 정확하게 센다. 오차는 지금은 0.01초 이내 인 것 같다.)
구체적인 알고리즘은 다음과 같다.
1. 우선 메세지를 보낼 시간을 저장하는 send_time 객체를 만든다.
2. 메세지를 수신하면 현재 시간을 변수에 저장한다. (년, 월, 일, 시, 분, 초, 밀리초 모두 저장한다.)
3. send_time 객체에 현재 시간에 5초를 더한 시간을 똑같이 각각 저장한다.
저렇게 년단위까지 모두 저장하는 이유는 2020년 12월 31일 23시 59분 59초에 톡을 받은 경우를 생각해보면 된다.
4. 쓰레드에 5초의 타이머를 건다.
5. 5초뒤 현재시간을 다시 변수에 저장한다.
6. 변수에 저장된 현재시간과 send_time 객체를 비교하여 현재 시간이 send_time 에 저장된 시간 이후라면
카톡보낸다.
만약 4번 과정으로인해 타이머가 걸려있는동안 상대방이 메세지를 더 보냈다면
그 쓰레드에의해 send_time의 시간이 새로 갱신될 것이다.
따라서 기존 쓰레드에 걸려있던 타이머가 풀리고 send_time과 현재시간을 비교하면
당연히 현재시간이 send_time 이전이다.
따라서 메세지를 보내지 않게 된다.
3. 오브젝트의 내용을 볼 수 있는 함수를 만들었다.
역시 파이썬은 세계 최고다
자바스크립트는 파이썬과 달리 오브젝트자체를 출력하면
[object Object] 뭐 이런식으로 자료형을 출력해버린다.
따라서 오브젝트 내용을 출력하려면 반복문을 돌려 일일히 값을 참조해야하는데,
이를 매번 코딩하기가 귀찮아서 아예 함수로 만들어버렸다.
디버깅용 말고도 지금까지 만난 사람들이나, 현재 자동응답 타이머가 걸려있는 사람들 목록보기 등등
내가 봇과 대화하면서 궁금한 내용을 쉽게 볼 수 있는 기능을 제공할 수 있도록 하였다.
'개인 프로젝트 > [2020] 카카오톡 봇' 카테고리의 다른 글
[카카오톡 봇 만들기] 3. 카카오링크 전송하기(1) : 테스트 & 봇 기능 수정 (3) | 2020.10.22 |
---|---|
[카카오톡 봇 만들기] 2. 명령어로 조작하기(3) : 테스트 (0) | 2020.10.18 |
[카카오톡 봇 만들기] 2. 명령어로 조작하기(2) : 알고리즘 구현(1) (0) | 2020.10.15 |
[카카오톡 봇 만들기] 2. 명령어로 조작하기(1) : 알고리즘 설계 (0) | 2020.10.12 |
[카카오톡 봇 만들기] 1. 수업시간 / 취침시간 자동응답 구현하기 (0) | 2020.10.10 |