카카오톡봇 + 외부API(1)

Kyoungchan Cho·2023년 1월 26일
1
post-thumbnail

intro

개발 생활스터디 그룹을 운영하면서 Notion으로 팀원들의 스터디 현황을 기록하고 공유하고 있다. 아래 두가지 항목을 메인으로 작성하지 못한 팀원 벌금을 내는 제도로 진행한다.

  1. 매일 정해진 시간(14:00)까지 투두 리스트 작성하기
  2. 일주일에 1회 이상 블로그 작성하기

정해진 시간마다 매번 노션페이지에 들어가서 확인하고 팀원들에게 알려주는 수동적인 절차를 줄이고,
실시간 소통 채널로 활용하고 있는 카카오톡 단톡방을 더욱 활성화하고자

노션(+외부) API를 활용하여 노션 페이지에 기록된 내용을 카카오톡에서 바로 확인할 수 있도록 카카오톡 봇을 만들고자 한다.

*프로젝트 레포 : https://github.com/whrod/mebot-kakao
*데모 영상 : youtube


프로젝트 구상

조건

  1. 오픈 톡방이 아닌 일반 단톡방에서 작동해야 한다.
    (오픈 톡방의 경우 카카오톡에서 제공하는 방장봇으로 쉽게 설정할 수 있다.)
  2. 1:1 개인 메세지가 아닌 단톡방에서 작동해야 한다.
    (1:1 메세지의 경우 카카오톡 공유 API나 카카오톡 메세지 API로 구현할 수 있다.)
  3. 24시간 작동해야 한다.
  4. API를 단순히 불러오는 것이 아닌 원하는 결과값을 커스텀할 수 있어야 한다.
  5. 노션 API 외에 추후에 기능 확장을 위해 다른 외부 API 연결을 쉽게 할 수 있어야 한다.

기능

  1. 단톡방의 멤버들이 입력한 커맨드에 대한 반응으로 원하는 결과값을 전달하는 기능
    (ex : 팀원, 투두리스트의 리스트, 투두리스트를 작성하지 않은 사람, 벌금자리스트 등)
  2. 특정한 시간에 작동하는 알람 메세지 기능
    (ex: 매일 아침 투두리스트 작성하라는 알람 메세지 기능 등)

사용 툴

안드로이드에서 작동하는 메신저봇R 어플리케이션과
node.js의 패키지인 remote-kakao를 활용한다.
메신저봇R은 조건 1, 2, 3 remote-kakao는 3,4,5를 충족시키기 위해 사용했다.

  • 메신저봇R
    안드로이드 휴대폰에 설치하여 상단에 뜨는 메세지 알림을 캐치하여 JS로 작성된 코드에 의해 커맨드로 입력하고 그에 대한 반응을 휴대폰에 설치된 카카오톡으로 전달한다.
    단점: 카카오톡 부계정 필요, 휴대폰 전원 및 화면 알람 상시 On 필수
  • remote-kakao
    node.js환경에서 원하는 서비스 로직을 구현할 수 있게 하고 EC2 등 클라우드에 배포하여 사용할 수 있다.
    메신저봇R만 사용하여 봇을 작동시킬 수 있지만 서비스 로직을 다양하게 구현하기 위해 remote-kakao를 사용한다.

초기세팅

  1. 봇 전용으로 사용할 여분의 안드로이드 휴대폰이 없는 상황으로
    카카오톡 부계정을 만들어 삼성 스마트폰의 듀얼메신저 기능으로 하나의 휴대폰에서 카카오톡 두개(본계정, 봇계정=부계정)를 운영한다.
  2. 휴대폰에 메신저봇R을 설치하고 remote-kakao의 아래 core-client부분을 메신저봇R의 봇 코드에 작성한다.
    * VSC의 ftp-simple 익스텐션을 사용하면 보다 쉽게 휴대폰에 코드를 작성할 수 있다.
"use strict";
var config = {
    address: '127.0.0.1', // Node.js 프로그램을 실행한 기기의 IP
    port: 3000,
};
var socket = new java.net.DatagramSocket();
var address = java.net.InetAddress.getByName(config.address);
var buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 65535);
var generateId = function (len) {
    var result = '';
    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (var _ = 0; _ < len; _++)
        result += chars[Math.floor(Math.random() * chars.length)];
    return result;
};
var getBytes = function (str) { return new java.lang.String(str).getBytes(); };
var inPacket = new java.net.DatagramPacket(buffer, buffer.length);
var sendMessage = function (event, data) {
    var bytes = getBytes(JSON.stringify({ event: event, data: data }));
    var outPacket = new java.net.DatagramPacket(bytes, bytes.length, address, config.port);
    socket.send(outPacket);
};
var sendReply = function (session, success, data) {
    var bytes = getBytes(JSON.stringify({ session: session, success: success, data: data }));
    var outPacket = new java.net.DatagramPacket(bytes, bytes.length, address, config.port);
    socket.send(outPacket);
};
var handleMessage = function (msg) {
    var _a;
    var _b = JSON.parse(decodeURIComponent(msg)), event = _b.event, data = _b.data, session = _b.session;
    switch (event) {
        case 'sendText':
            var res = Api.replyRoom(data.room, ((_a = data.text) !== null && _a !== void 0 ? _a : '').toString());
            sendReply(session, res);
            break;
    }
};
var send = function (msg) {
    sendMessage('chat', {
        room: msg.room,
        content: msg.msg,
        sender: msg.sender,
        isGroupChat: msg.isGroupChat,
        profileImage: msg.imageDB.getProfileBase64(),
        packageName: msg.packageName,
    });
};
var response = function (room, msg, sender, isGroupChat, _, imageDB, packageName) { return send({ room: room, msg: msg, sender: sender, isGroupChat: isGroupChat, imageDB: imageDB, packageName: packageName }); };
// @ts-ignore
var thread = new java.lang.Thread({
    run: function () {
        while (true) {
            socket.receive(inPacket);
            handleMessage(String(new java.lang.String(inPacket.getData(), inPacket.getOffset(), inPacket.getLength())));
        }
    },
});
var onStartCompile = function () { return thread.interrupt(); };
thread.start();

  1. node.js환경에서 remote-kakao와 메세지를 수신하면 콘솔에 로그해주는 플러그인을 설치한다.
// index.ts
import { Server } from '@remote-kakao/core';
import LoggerPlugin from './plugins/logger'; //플러그인 추가

const prefix = '>';
const server = new Server();

server.usePlugin(LoggerPlugin); //플러그인 추가

server.on('message', async (msg) => {
  if (!msg.content.startsWith(prefix)) return;

  const args = msg.content.split(' ');
  const cmd = args.shift()?.slice(prefix.length);

  if (cmd === 'ping') {
    /*
      메신저봇과 카카오톡 서버간의 핑이 아닌,
      Node.js와 메신저봇간의 핑입니다.
    */
    const timestamp = Date.now();
    await msg.reply('Pong!');
    msg.reply(`${Date.now() - timestamp}ms`);
  }
});

server.start(3000);
// plugins/logger.ts
import { Message, RKPlugin } from '@remote-kakao/core';

class LoggerPlugin extends RKPlugin {
  onMessage = async (msg: Message) => {
    this.log(`${msg.sender.name}: ${msg.content}`);
  };
}

export default LoggerPlugin;

  1. 설치 후 실행 테스트 화면



참고)
https://github.com/remote-kakao

profile
https://lying-lettuce-69f.notion.site/KyoungchanCho-Blog-f9f150b9e3be4467a67cf2a21932650d (게시글 자동 비공개 현상으로 일단 노션으로 이동합니다. 소개에서 URL 링크 클릭으로 연결됩니다.)

4개의 댓글

comment-user-thumbnail
2024년 1월 26일

안녕하세요! 카카오톡 챗봇을 만드려고 했는데 공기계가 따로 없어서 고민하던 차에 발견했습니다! 삼성 듀얼메신저 기능 사용하신다고 하셨는데 카톡은 번호 2개가 있어야 한다더라구요ㅠㅠ 혹시 번호는 어떻게 하셨는지 알 수 있을까요??

1개의 답글
comment-user-thumbnail
2024년 4월 11일

안녕하세요. node.js 연결해보려고하는데, ftp-simple로 어떻게 모바일에 파일 작성하셨는지 궁금합니다. 모바일에 ftp서버 열어두신건가요

답글 달기
comment-user-thumbnail
2024년 10월 14일

EC2 보다 lambda 가 낫지 않을까요

답글 달기