본문 바로가기

BLOG/오픈소스 리뷰기

[오픈소스 리뷰기] 나만의 텔레그램 챗봇 만들기!

오픈소스 리뷰 : 슬기로운 오픈소스 사용법 리뷰해드립니다!
#12 나만의 텔레그램 챗봇 만들기!

 

 

안녕하세요. 디노랩스입니다!

오늘 다뤄볼 오픈소스는 우리 일상생활에도 흔히 볼 수 있는 "챗봇"입니다.

 

챗봇(Chatbot)은 사용자가 컴퓨터와 대화하듯이 정보를 주고받는 인터페이스로 정보 안내·일정 관리·티켓 예매, 쇼핑 등 다양한 서비스에 이용될 수 있으며, 텔레그램·위챗·카카오톡 등의 다양한 메시지 플랫폼에서 구현할 수 있습니다. 메시지 플랫폼은 대부분 챗봇 인터페이스 구현을 위한 웹 API를 제공하고 있는데 이번 실습에서는 텔레그램을 활용하여 간단한 챗봇을 만들고 몇가지 기본기능에 대해 알아보겠습니다!

활용 파트에서는 공공API를 활용하여 코로나 알림 챗봇을 만들어 보고, 이번 실습을 완료한 뒤 활용파트와 같이 다른 API들과 결합하여 나만의 챗봇을 만들어보세요😀

 

 

 

01_봇 생성하기

 

가장 먼저 PC 텔레그램 또는 앱을 설치한 후 BotFather를 검색합니다. 이름이 비슷한 다른 채널들에 유의하세요.

 

 

이후 시작 버튼을 클릭하면 아래와 같이 봇파더에게 말할 수 있는 여러가지 명령어들을 안내합니다.

 

 

 

새로운 봇을 만들기 위해서는 여기서 /newbot 이라고 입력해줍니다.

그러면 아래와 같이 새로 만들 봇을 위한 이름을 정하라고 하는데요,

쉽게 말해 카톡으로 치면 채팅방의 이름을 짓는다고 생각하면 됩니다.

 

 

 

우선 가장 먼저 테스트용으로 테스트용봇 이라고 이름을 지어보겠습니다.

그러면 아래와 같이 봇의 반드시 bot이라는 단어로 끝나는 username(계정명)을 입력하라는 메세지를 보여줍니다.

 

 

 

이번에는 Ardino_test_bot이라는 이름으로 username을 입력하겠습니다.

(test_bot 과 같이 중복되는 이름이 존재하면 다시 입력하라는 메세지를 보냅니다. 중복되지 않은 username을 입력해주세요.)

이전에 입력한 테스트용봇은 쉽게 말해 채팅방 이름이고, 지금 새로 생성할 username은 채팅방에서 사용되는 진짜 봇의 이름이니 구분하세요!!

 

 

 

username을 입력하여 봇을 최종적으로 생성하게 되면 API 를 사용하는데 필요한 토큰을 확인할 수 있습니다.
다음으로는 방금 만든 봇 이름을 검색해서 친구 추가를 해야합니다.
검색창에 이전에 입력한 username(Ardino_test_bot) 을 입력하여 시작버튼을 눌러준 뒤 첫번째 메세지를 보내보세요.

 

 

하지만 당연히 챗봇은 아무 답도 안합니다. 우리가 아직 아무 기능도 안 넣어 줬으니 아무런 답을 안하는 것이 당연합니다!!
이제 다양한 기능들을 추가해보겠습니다.😊

 

 

 

 

02_기능 추가하기

 

이제 텔레그램 봇이 생성되었으니 기능들을 추가해보겠습니다.
우선 python 에서 사용가능한 텔레그램 봇 라이브러리를 설치한 뒤 이를 불러와 bot 인스턴스를 생성해보겠습니다.

 

 

!pip install python-telegram-bot

 

# 런타임을 재시작합니다.
import os
os.kill(os.getpid(), 9)

 

import telegram
 
token = '아까_발급_받은_API_토큰을_넣어주세요'
bot = telegram.Bot(token=token)

 

 

이제 봇 인스턴스가 생성되었고 이후에 사용될 사용자 대화 id를 찾기 위해 아래 코드를 실행합니다.
혹시 실행을 한 뒤에도 print 된 결과가 없거나 에러가 발생한다면 챗봇에서 몇 마디를 건네본 다음 다시 아래 코드를 실행해보세요.

 

 

updates = bot.getUpdates()
for u in updates:
    print(u.message)
{'message_id': 4, 'photo': [], 'group_chat_created': False, 'new_chat_members': [], 'channel_chat_created': False, 'entities': [], 'delete_chat_photo': False, 'caption_entities': [], 'date': 1636385414, 'text': '하이', 'new_chat_photo': [], 'supergroup_chat_created': False, 'chat': {'first_name': '윤성', 'type': 'private', 'id': 2145415566}, 'from': {'is_bot': False, 'language_code': 'ko', 'id': 2145415566, 'first_name': '윤성'}}


실행 결과를 살펴보면 messege_id, date, id 등 다양한 정보를 가지고 있는데 기본적인 정보만 알아보겠습니다.

  • message_id : 몇 번째 메세지 인지 나타냄
  • id : 챗봇 사용자 대화 id
  • date : 메세지 전송 시간
  • first_name : 챗봇 사용자 이름

이후에 챗봇에게 메세지를 보내는 경우 사용자 대화 id를 사용하므로 따로 변수에 저장합니다.

 

# 아래에 id를 입력하세요
id = "###########id를 입력하세요###########"

 

 

*봇에 나에게 메세지 보내게 하기

 

이제 token과 id를 모두 구했으니 가장 먼저 봇이 나에게 메세지를 보내는 기능을 알아보겠습니다.
봇이 나에게 메시지를 보내게 하는 메서드는 sendMessage 입니다.
아래 코드를 실행시켜 확인해보세요.

 

import telegram

bot = telegram.Bot(token = token)
bot.sendMessage(chat_id=id, text="테스트 메세지")

 

 

위 코드를 실행하면 text에 입력한 내용을 채팅방에 챗봇이 아래와 같이 전송하는 것을 확인할 수 있습니다.

 

 

 

 

*내가 보낸 메세지에 따른 자동응답 만들기

 

 

이제 내가 메세지를 보냈을때, 이를 챗봇이 읽은 뒤, 그 메세지에 맞는 답장을 보내보도록 하겠습니다.
아래 코드에서 handler 함수만 수정하여 다양한 조건을 추가할 수 있으며, 아래 코드를 실행한 뒤 조건에 맞는 메세지를 입력하면 그에 따른 자동응답 하는 것을 확인할 수 있습니다.

 

 

import telegram
from telegram.ext import Updater
from telegram.ext import MessageHandler, Filters
 
token = "###########토큰을 입력하세요###########"
id = "###########id를 입력하세요###########"
 
bot = telegram.Bot(token)
 
# updater
updater = Updater(token=token, use_context=True)
dispatcher = updater.dispatcher
updater.start_polling()
 

def handler(update, context):
    """
    사용자가 보낸 메세지를 읽어들이고, 답장을 보내는 함수입니다.
    """
    # 사용자가 보낸 메세지를 user_text 변수에 저장합니다.
    user_text = update.message.text

    # 사용자가 보낸 메세지가 "안녕"일 경우
    if user_text == "안녕": 
        bot.send_message(chat_id=id, text="안녕, 반가워😀")
    
    # 사용자가 보낸 메세지가 "뭐해"일 경우
    elif user_text == "뭐해":
        bot.send_message(chat_id=id, text="너랑 대화중")
 
echo_handler = MessageHandler(Filters.text, handler)
dispatcher.add_handler(echo_handler)

 

이제 아래와 같이 안녕뭐해 를 입력하면 이를 인식하고 사전에 지정한 조건에 따라 메세지를 보내는 것을 확인할 수 있습니다.

 

 

 

*버튼 추가하기

 

이번에는 자주 사용하는 기능이나 메세지를 버튼으로 만들어 손쉽게 사용할 수 있도록 해보겠습니다.
아래 코드의 cmd_task_buttons에서 버튼을 수정할 수 있으며 cb_button을 통해 버튼을 클릭했을때 실행되는 조건을 변경할 수 있습니다.
우선 가장 기본적인 실습으로 버튼을 클릭했을때 작업을 완료했다는 메세지를 보내도록 해보겠습니다.

 

 

import telegram
 
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ChatAction
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler
 
token = "###########토큰을 입력하세요###########"
id = "###########id를 입력하세요###########"
 
bot = telegram.Bot(token = token)
updater = Updater(token = token)
dispatcher = updater.dispatcher

def cmd_task_buttons(update, context):
    """
    task 버튼을 추가하는 함수입니다.
    """
    task_buttons = [
        [
        InlineKeyboardButton("작업1", callback_data=1 ), InlineKeyboardButton("작업2", callback_data=2 )
        ], 
        [
        InlineKeyboardButton("작업3", callback_data=3 ), InlineKeyboardButton("작업4", callback_data=4 )
        ], 
        [
        InlineKeyboardButton("종료", callback_data=9 )
        ]
    ]
    reply_markup = InlineKeyboardMarkup( task_buttons )
    context.bot.send_message(
        chat_id=update.message.chat_id,
        text="작업을 선택해주세요.",
        reply_markup=reply_markup
    )

def cb_button(update, context):
    """
    버튼을 클릭했을 때 실행되는 함수 입니다.
    """
    query = update.callback_query
    data = query.data
    
    context.bot.send_chat_action(
        chat_id=update.effective_user.id,
        action=ChatAction.TYPING
    )

    if data == "9":
        context.bot.edit_message_text(
            text=f"작업이 종료되었습니다.",
            chat_id=query.message.chat_id,
            message_id=query.message.message_id
        )
    else:
        context.bot.edit_message_text(
            text=f"[{data}] 작업을 완료하였습니다.",
            chat_id=query.message.chat_id,
            message_id=query.message.message_id
        )

 
def add_handler(cmd, func):
    """
    버튼을 나타내기 위한 명령어를 추가하는 함수 입니다.
    """
    updater.dispatcher.add_handler(CommandHandler(cmd, func))
 
def callbsck_handler(func):
    """
    handler를 추가하는 함수입니다.
    """
    updater.dispatcher.add_handler(CallbackQueryHandler(func))


add_handler("task", cmd_task_buttons)
callbsck_handler(cb_button)
updater.start_polling()

 

위 코드를 실행한 뒤 챗봇에게 /task라고 메세지를 보내게 되면 아래와 같이 cmd_task_buttons 에서 설정한대로 버튼이 생성되는 것을 확인할 수 있으며 이를 클릭할 경우 cb_button 에서 설정항대로 작업을 완료했다는 메세지를 보내는 것을 확인할 수 있습니다.

 

 

이제 이와 같은 기본 기능들을 활용하여 실제로 간단한 챗봇을 구현해보겠습니다.

 

 

 

 

(활용) 공공데이터 API활용 코로나 알림이

 

이전 공공데이터포털 API 활용에서 지역별 코로나19 감염현황 데이터를 불러온 것을 기억하시나요?:D
이번에는 텔레그램 챗봇과 공공데이터포털API를 결합하여 지역별 현황을 손쉽게 불러오는 코드를 작성해보겠습니다.
아래 링크에 있는 API에 대해 활용신청을 완료한 뒤 코드를 실행하세요

https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15043378

 

공공데이터활용지원센터_보건복지부 코로나19 시·도발생 현황

코로나19감염증으로 인한 시.도별 신규확진자,신규사망자,격리중인환자수,격리해제환자수등에 대한 현황자료 (이 제공자료는 관련 발생 상황에 대한 정보를 신속 투명하게 공개하기 위한 것으

www.data.go.kr

 

가장 먼저 1번에서 진행했던 것을 활용하여 아래와 같이 새로운 봇을 생성한 뒤 검색창에 username을 입력하여 채팅방에서 새로운 메세지를 보내는 과정까지 완료해주세요.

 

 

우선 사용할 라이브러리를 import 하겠습니다.

 

 

!pip install python-telegram-bot

 

# 런타임을 재시작합니다.
import os
os.kill(os.getpid(), 9)

 

import telegram
from telegram.ext import Updater
from telegram.ext import MessageHandler, Filters

from bs4 import BeautifulSoup
from urllib.request import Request, urlopen
from urllib.parse import urlencode, quote_plus
from xml.etree import ElementTree
import pandas as pd
from datetime import datetime, timedelta
import pytz
import os

 

 

다음으로는 공공데이터포털 API를 활용하여 전날 00시 기준 지역별 코로나 확진자수를 가져오는 함수를 작성하겠습니다.

 

 

def get_covid_count():
    url = 'http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson'
    service_key = '###############공공데이터 포털의 서비스키를 입력하세요###############'

    # 한국의 timezone 정보를 가져와 현재 기준 어제 날짜를 구합니다.
    tz = pytz.timezone("Asia/Seoul")
    now = datetime.now(tz)
    yesterday = (now - timedelta(1)).strftime("%Y%m%d")

    params = '?' + \
        urlencode({
            quote_plus('ServiceKey') : service_key,
            quote_plus('pageNo') : '0',
            quote_plus('startCreateDt') : yesterday,
            quote_plus('endCreateDt') : yesterday,
        })
    request = Request(url + params) # url과 요청파라미터를 결합하여 서버에 데이터를 요청합니다.
    response_body = urlopen(request).read() # response 를 text 형태로 읽어옵니다.
    root = ElementTree.fromstring(response_body) # text를 XML 형태로 변환합니다.

    df = pd.DataFrame()
    for item in root.iter('item'): # XML 구조에 있는 <item>에서 필요한 정보들만 수집합니다.
        item_dict = {}
        item_dict['시도명'] = item.find('gubun').text
        item_dict['확진자'] = item.find('defCnt').text
        item_dict['전일대비증감'] = item.find('incDec').text
        df = df.append(item_dict, ignore_index = True)
    df[['전일대비증감']] = df[['전일대비증감']].apply(pd.to_numeric)
    df = df.sort_values(by="전일대비증감", ascending=False)

    result = f'{(now - timedelta(1)).strftime("%Y-%m-%d")} 기준\n'
    for sido, inc_dec in zip(df["시도명"], df["전일대비증감"]):
        result += f'{sido}\t{inc_dec}\n'
    
    return result

 

 

다음으로는 코로나 관련 기사를 크롤링하는 함수를 작성해보겠습니다.

 

def get_covid_news():
    code = urlopen("https://search.naver.com/search.naver?where=news&sm=tab_jum&query=%EC%BD%94%EB%A1%9C%EB%82%98")
    soup = BeautifulSoup(code, "html.parser")
    title_list = soup.select("a.news_tit")
    output_result = ""
    for i in title_list:
        title = i.text
        news_url = i.attrs["href"]
        output_result += title + "\n" + news_url + "\n\n"
        if title_list.index(i) == 2:
            break
    return output_result

 

 

이제 위에서 작성한 get_covid_count함수와 get_covid_news함수를 활용하여 텔레그램 봇에게 메세지를 보내는 코드를 작성해보겠습니다.

 

token = "###########토큰을 입력하세요###########"
id = "###########id를 입력하세요###########"

bot = telegram.Bot(token)

# bot을 열었을때 보내는 메세지 입니다.
info_message = """
- 어제 확진자 수 확인 : '코로나' or 'ㅋㄹㄴ' 입력
- 코로나 관련 뉴스 : '뉴스' or 'ㄴㅅ' 입력
"""

bot.sendMessage(chat_id=id, text=info_message)
 
updater = Updater(token=token, use_context=True)
dispatcher = updater.dispatcher
updater.start_polling()
 
### 챗봇 답장
def handler(update, context):

    # 사용자가 보낸 메세지를 user_text 변수에 저장합니다.
    user_text = update.message.text

    # 확진자 수
    if (user_text == "코로나") or (user_text == "ㅋㄹㄴ"):
        covid_num = get_covid_count()
        bot.send_message(chat_id=id, text=covid_num)
        bot.sendMessage(chat_id=id, text=info_message)
    # 코로나 관련 뉴스
    elif (user_text == "뉴스") or (user_text == "ㄴㅅ"):
        covid_news = get_covid_news()
        bot.send_message(chat_id=id, text=covid_news)
        bot.sendMessage(chat_id=id, text=info_message)
 
echo_handler = MessageHandler(Filters.text, handler)
dispatcher.add_handler(echo_handler)

 

위 코드를 실행한 뒤 챗봇에게 (코로나ㅋㄹㄴ) 혹은 (뉴스ㄴㅅ)라고 메세지를 보내게 되면 아래와 같이 실행결과를 확인할 수 있습니다.

 

 

 

이번에는 /covid라는 명령어를 입력할 경우 확진자 수와 뉴스를 선택할 수 있는 버튼을 추가해보겠습니다.

 

 

!pip install python-telegram-bot

 

# 런타임을 재시작합니다.
import os
os.kill(os.getpid(), 9)

 

import telegram 
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ChatAction
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler

from bs4 import BeautifulSoup
from urllib.request import Request, urlopen
from urllib.parse import urlencode, quote_plus
from xml.etree import ElementTree
import pandas as pd
from datetime import datetime, timedelta
import pytz
import os

 

token = "###########토큰을 입력하세요###########"
id = "###########id를 입력하세요###########"
 
bot = telegram.Bot(token = token)
updater = Updater(token = token)
dispatcher = updater.dispatcher


def get_covid_count():
    url = 'http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson'
    service_key = '###############공공데이터 포털의 서비스키를 입력하세요###############'

    # 한국의 timezone 정보를 가져와 현재 기준 어제 날짜를 구합니다.
    tz = pytz.timezone("Asia/Seoul")
    now = datetime.now(tz)
    yesterday = (now - timedelta(1)).strftime("%Y%m%d")

    params = '?' + \
        urlencode({
            quote_plus('ServiceKey') : service_key,
            quote_plus('pageNo') : '0',
            quote_plus('startCreateDt') : yesterday,
            quote_plus('endCreateDt') : yesterday,
        })
    request = Request(url + params) # url과 요청파라미터를 결합하여 서버에 데이터를 요청합니다.
    response_body = urlopen(request).read() # response 를 text 형태로 읽어옵니다.
    root = ElementTree.fromstring(response_body) # text를 XML 형태로 변환합니다.

    df = pd.DataFrame()
    for item in root.iter('item'): # XML 구조에 있는 <item>에서 필요한 정보들만 수집합니다.
        item_dict = {}
        item_dict['시도명'] = item.find('gubun').text
        item_dict['확진자'] = item.find('defCnt').text
        item_dict['전일대비증감'] = item.find('incDec').text
        df = df.append(item_dict, ignore_index = True)
    df[['전일대비증감']] = df[['전일대비증감']].apply(pd.to_numeric)
    df = df.sort_values(by="전일대비증감", ascending=False)

    result = f'{(now - timedelta(1)).strftime("%Y-%m-%d")} 기준\n'
    for sido, inc_dec in zip(df["시도명"], df["전일대비증감"]):
        result += f'{sido}\t{inc_dec}\n'
    
    return result


def get_covid_news():
    code = urlopen("https://search.naver.com/search.naver?where=news&sm=tab_jum&query=%EC%BD%94%EB%A1%9C%EB%82%98")
    soup = BeautifulSoup(code, "html.parser")
    title_list = soup.select("a.news_tit")
    output_result = ""
    for i in title_list:
        title = i.text
        news_url = i.attrs["href"]
        output_result += title + "\n" + news_url + "\n\n"
        if title_list.index(i) == 2:
            break
    return output_result


def cmd_task_buttons(update, context):
    """
    task 버튼을 추가하는 함수입니다.
    """
    task_buttons = [
        [
        InlineKeyboardButton("어제 확진자", callback_data=1 ), InlineKeyboardButton("코로나 뉴스", callback_data=2 )
        ],
        [
        InlineKeyboardButton("종료", callback_data=9 )
        ]
    ]
    reply_markup = InlineKeyboardMarkup( task_buttons )
    context.bot.send_message(
        chat_id=update.message.chat_id,
        text="작업을 선택해주세요.",
        reply_markup=reply_markup
    )

def cb_button(update, context):
    """
    버튼을 클릭했을 때 실행되는 함수 입니다.
    """
    query = update.callback_query
    data = query.data
    
    context.bot.send_chat_action(
        chat_id=update.effective_user.id,
        action=ChatAction.TYPING
    )

    if data == "9":
        context.bot.edit_message_text(
            text=f"작업이 종료되었습니다.",
            chat_id=query.message.chat_id,
            message_id=query.message.message_id
        )
    elif data == "1":
        covid_num = get_covid_count()
        context.bot.edit_message_text(
            text=covid_num,
            chat_id=query.message.chat_id,
            message_id=query.message.message_id
        )
    elif data == "2":
        covid_news = get_covid_news()
        context.bot.edit_message_text(
            text=covid_news,
            chat_id=query.message.chat_id,
            message_id=query.message.message_id
        )
 
def add_handler(cmd, func):
    """
    버튼을 나타내기 위한 명령어를 추가하는 함수 입니다.
    """
    updater.dispatcher.add_handler(CommandHandler(cmd, func))
 
def callbsck_handler(func):
    """
    handler를 추가하는 함수입니다.
    """
    updater.dispatcher.add_handler(CallbackQueryHandler(func))


add_handler("covid", cmd_task_buttons)
callbsck_handler(cb_button)
updater.start_polling()

 

위 코드를 실행한 뒤 코로나봇에게 /covid라는 명령어를 보내면 아래와 같이 버튼이 활성화되며 버튼을 클릭할 경우 각각의 조건에 맞는 메세지를 코로나봇이 보낼 수 있게 됩니다.

 

 

텔레그램을 활용한 나만의 챗봇 만들기! 어떠셨나요? 

어렵게만 느껴지던 우리 삶 주변의 다양한 인공지능 서비스들, 직접 만들어보니까 훨씬 친근하지 않나요? :D

그럼 오늘의 오픈소스 리뷰기 마치겠습니다!