파이썬+AWS Lambda+AWS API Gateway 텔레그램 봇 개발, 배포까지

dtrtetg·2021년 2월 22일
24

Telegram Bot

목록 보기
1/1
post-thumbnail

시작하기 전에

안녕하세요. 드래곤타이거 입니다.

와이프가 운영하는 카카오톡 오픈톡방이 있었는데 1500명 정원이 모두 차버렸습니다. 그래서 방법이 없을까 고민하던 중 텔레그램 그룹에서는 20만명 까지 참여가 가능하다는 정보를 알게 되었습니다. 카톡에는 팬더Jr가 있어서 어느정도 공지사항 안내가 가능했는데 텔레그램에서는 더욱 커스터마이징이 가능한 API를 제공한다고 하여 직접 챗봇을 만들어보기로 했습니다.

참고로 저는 프로그래밍 언어는 Java 웹 개발자를 목표로 공부하던 중 이라서 파이썬에 대해서 익숙하지 않습니다. 코딩 규칙 등 잘못 작성한 것이 있다면 댓글로 알려주시면 감사하겠습니다.

사전 지식

  1. 파이썬
  2. 클라우드 컴퓨팅, AWS 람다에 대한 개념 (서버리스가 뭔지 유튜브 영상 아무거나 봐도 될듯)
  3. HTTP 통신에 대한 개념 (URL, 요청, 응답 등? 이런 개념들)
  4. API 개념

구상해보기

API, 블로그 등을 찾아보며 제가 이해한 개념을 적은 것이니 만약 틀린 부분이 있다면 댓글로 지적해주시면 감사하겠습니다.

그림에 적힌 숫자 순서대로 자세히 설명을 해보겠습니다.
우선 텔레그램 서버와 봇API를 구분한 이유는 단지 이해를 돕기위한 저만의 설정입니다. Telegram Server는 채팅을 하는 공간이고, Telegram Bot API는 봇 관련 API가 동작하는 백엔드? 같은 개념으로 이해해주시면 됩니다.

  1. 유저가 텔레그램 메신저를 통해서 메시지 혹은 명령어를 입력한다.
  2. 메시지가 입력되면 웹훅 이라는 기능이 작동한다. 이 기능은 미리 등록한 URL을 호출한다.
  3. 위 URL은 AWS의 API Gateway 서비스를 통해서 생성된 것으로 해당 서비스를 호출한다.
  4. API Gateway와 Lambda 서비스를 연동시켜두어 람다 함수를 호출한다.
  5. 람다 함수는 유저가 보낸 메시지를 분석해서 무시할지, 명령어에 대한 결과를 반환할 지 결정한다.
  6. 명령어에 대한 결과가 있다면 그 결과를 메시지에 담아 보낸다. 그럼 유저들이 메시지를 채팅방을 통해 확인할 수 있게 된다.

구현하기

봇 아빠한테 봇 만들어달라고 하기

텔레그램은 특이하게도 봇을 만드는 방법이 봇을 통해 만드는 것입니다. @BotFather라는 봇이 봇을 만들어줍니다. 직역하면 봇 아빠ㅋㅋ

친구 찾기에 BotFather 라고 검색해주면 찾을 수 있습니다. 사진에 보이는 것 처럼 제일 위에 @BotFather 를 찾아서 시작하기 버튼 눌러주세요.

그러면 명령어 리스트들이 쭉 보입니다. 나중에 자신의 봇에 대한 설정을 이 봇파더를 통해서 할 수 있습니다. 우리는 새로운 봇을 만들 것이기 때문에 @newbot 명령어를 입력해줍니다.

나 : /new bot
봇파더 : Alright, a new bot. How are we going to call it? Please choose a name for your bot.

그럼 봇 이름을 정하라고 합니다. 저는 제 닉네임인 dragontiger로 해보겠습니다.

나 : dragontiger
봇파더 : Good. Now let's choose a username for your bot. It must end in bot. Like this, for example: TetrisBot or tetris_bot.

이제 username을 정하라고 합니다. 이건 무조건 끝이 bot, Bot 등으로 끝나야 한다고 합니다.
저는 dragontiger_bot 으로 해보겠습니다.

이 이름은 중복되니 사용할 수 없다고 하네요. 다시 dragontiger_dev_bot 으로 해보겠습니다.

중복되지 않으며 끝자리가 bot으로 끝나게 잘 생성이 되면 다음과 같은 메시지를 보여줍니다.
빨간색 네모칸에 있는 문자열은 우리가 만든 봇의 토큰입니다. 이 토큰을 통해서 웹훅을 등록하고 메시지를 보내는 등 API를 이용할 수 있습니다.

지금부터 토큰이라고 말하면 위에 빨간 네모 표시로 해둔 문자열을 말하는 것으로 알아주세요.
(예시 : 1234567890:ABCD012310231ASDASDASDklkkkk)

다른 사람에게 노출된다면 본인의 봇을 제어할 수 있게 되니 깃헙같은 곳에 올리지 않도록 주의해주세요.

이제 봇 생성은 끝났습니다. 이제 부여받은 토큰을 이용해서 간단하게 메시지를 보내는 테스트를 해보겠습니다.

봇 API 익히기

공식 API 문서 찾아보기

블로그 글 말고 공식 API를 찾아보는 습관을 조금씩 길러야 합니다. 저도 누군가가 쉽게 풀어쓴 블로그 글을 먼저 보긴 하지만 조금 익숙해지면 API 문서를 찾아서 더 자세한 내용을 보곤 합니다.

텔레그램 API 사용법 링크 : https://core.telegram.org/bots/api#making-requests

위 사진은 공식 API 문서를 캡쳐한 것입니다. API를 어떻게 요청하는지 알려주는 내용인데요.
https://api.telegram.org/bot<token>/METHOD_NAME
위 URL 규칙대로 GET, POST 방식의 요청 모두 지원이 가능하다고 합니다. 또한 파라미터를 전달하는 방법도 4가지가 있으니 파일 업로드(사진 전송, 파일 전송 등) 기능이 필요하신 분은 참고하시면 되겠습니다.

sendMessage API 찾아보기

sendMessage API 링크 : https://core.telegram.org/bots/api#sendmessage

링크에 들어가보면 메시지를 보내기 위한 API가 정의되어 있습니다. 우리가 메시지를 보내기 위해 필요한 것은 무엇이 있을까요?

받는 사람과 메시지에 담길 내용이 필요합니다. 공식 문서에도 필수라고 적혀있네요. chat_id, text 외에 나머지는 필요한 옵션을 선택해서 쓰면 되는데 재미난 기능이 많으니 시간나면 해보시길 권합니다.

테스트 메시지 보내보기

위에서 익한 API를 이용해서 봇으로 본인 아이디에 메시지를 보내보겠습니다. 봇이 사용자에게 메시지를 보내려면 먼저 사용자가 봇에게 말을 걸어야 합니다. 본인이 만든 봇을 찾아서 봇이 작동되게 해주세요.

메시지를 보내는 URL을 만들어보면 아래 형식처럼 될겁니다.
https://api.telegram.org/bot<token>/METHOD_NAME

https://api.telegram.org/bot0123456789:AAbbccddeeffgg0aabbccddeeff0123abc/sendMessage?chat_id=???&text=hello

주의할 점은 본인이 만든 봇의 토큰을 적어주셔야 하고 토큰 앞에 bot을 꼭 적어주셔야 해요.그럼 메시지를 보내는 URL은 이런 형식이 될겁니다. 아이디는 2가지 방법으로 찾을 수 있는데요.


사진처럼 userinfobot 을 검색해서 /start 명령어를 보내면 제 아이디를 알려줍니다. 숫자로 된 id를 쿼리스트링으로 넘겨줍시다.

https://api.telegram.org/bot0123456789:AAbbccddeeffgg0aabbccddeeff0123abc/sendMessage?chat_id=123456789&text=hello

그럼 만들어진 URL 호출을 해볼까요?

그냥 브라우저 주소창에 붙여넣기 해도 Postman 같은 API Test 도구를 이용하셔도 됩니다. 저는 크롬 확장 프로그램 Talend Api Tester를 이용합니다.

링크 : talend-api-tester

봇이 저에게 메시지를 보낸 것을 확인해볼 수 있습니다! 이제 이걸 파이썬 코드로 보내볼게요.

파이썬으로 메시지 보내기

코드

import http.client
import json

TELEGRAM_API_HOST = 'api.telegram.org'
TOKEN = '0123456789:AAbbccddeeffgg0aabbccddeeff0123abc'

connection = http.client.HTTPSConnection(TELEGRAM_API_HOST)

# 토큰과 메서명 지정
url = f"/bot{TOKEN}/sendMessage"

# HTTP 헤더
headers = {'content-type': "application/json"}

# 파라미터
param = {
    'chat_id': 577386516,
    'text': 'python 에서 보냄'
}

# Http 요청
connection.request("POST", url, json.dumps(param), headers)

# 응답
res = connection.getresponse()

# Response body 출력
print(json.dumps(json.loads(res.read().decode()), indent=4))
print('응답코드 : ', res.status)
print('메시지 : ', res.msg)

# 연결 끊기
connection.close()

텔레그램 관련 좋은 라이브러리들이 있습니다만 람다 서비스에 코드를 올릴 때 해당 라이브러리 파일도 같이 업로드 해주어야 해서 최대한 기본적인? 코드로만 작성했습니다. (업로드 해봤는데 잘 안되서 방법을 바꿨습니다...)

주의할 점은 마지막 줄에 HTTP 연결을 끊어주는 것인데요. 정상 동작 했을 때에는 문제가 없지만 오류가 발생한다던지, 메시지가 잘 보내지지 않았을 때 비정상적인 요청을 합니다.

저같은 경우 명령어가 아닌 메시지에는 API를 호출하지 않도록 했는데 연결이 계속 유지가 되어있어서 텔레그램 봇이 1분마다 마지막으로 인지했던 명령어를 계속 응답하는 현상이 있었습니다. 찾는데 한 3시간 걸렸네요 ㅠ

메시지가 잘 보내졌으면 res.read()로 응답을 볼 수 있는데 반환 타입이 byte 입니다. 그래서 바이트코드 ➡ 문자열 ➡ Json ➡ (출력했을때 보기 좋은) 문자열로 변환한 코드 입니다. 필요에 맞게 수정해서 쓰시면 됩니다.

실행 결과

{
    "ok": true,
    "result": {
        "message_id": 25,
        "from": {
            "id": 012345678,
            "is_bot": true,
            "first_name": "dragontiger",
            "username": "dragontiger_dev_bot"
        },
        "chat": {
            "id": 012345678,
            "first_name": "dragontiger",
            "username": "dragontiger_dev",
            "type": "private"
        },
        "date": 1613956418,
        "text": "python \uc5d0\uc11c \ubcf4\ub0c4"
    }
}

응답코드 :  200
메시지 :  Server: nginx/1.16.1
Date: Mon, 22 Feb 2021 01:13:38 GMT
Content-Type: application/json
Content-Length: 294
Connection: keep-alive
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Expose-Headers: Content-Length,Content-Type,Date,Server,Connection

봇이 메시지를 보낸 것도 확인이 됐고 응답 결과도 확인이 됩니다.
누가 누구에게 보낸 것인지 뭐라고 보낸 것인지 확인할 수 있습니다. 뭐라고 보냈는지가 'text' 키에 담겨있는데요 내용을 보면 한국말이 아니죠? 바로 유니코드 입니다. 유니코드 검색 사이트에서 해당 코드 조회해봐도 되고 직접 변환해서 봐도 됩니다.

이제 코드가 잘 작동하니 AWS 람다 함수에 작성한 코드를 등록해보겠습니다.

AWS Lambda 구현하기

AWS Lambda 링크

링크를 통해서 람다 서비스 페이지로 이동해주세요.

함수 생성 버튼 눌러주세요.

함수 이름 규칙에 맞게 마음대로 적어주시고 런타임은 파이썬으로 선택해주세요. 그다음 함수 생성 버튼 클릭! 한 10~20초 기다리면 아래 화면이 나옵니다.

상단에 있는 그림은 현재 서비스가 어떤 방식으로 동작하는지 간단하게 그림으로 보여주는 공간입니다. 그리고 아래쪽에 함수 코드라고 적힌 부분이 파이썬 코드를 작성하는 부분입니다.

lambda_function.py 파일 열어보시면 아래와 같은 코드가 기본적으로 작성되어있습니다.

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

람다 함수는 기본 설정으로 lambda_function 파일에 있는 lambda_handler함수를 실행합니다.

우선 전달인자로 어떤 정보가 들어오는지 확인해볼게요.

import json

def lambda_handler(event, context):
    # TODO implement
    
    print("event : ", event)
    print("context : ", context)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

그리고 테스트 버튼 눌러주시면 팝업이 하나 생길겁니다.

테스트에 파라미터로 어떤 값을 전달할지를 적어주는건데요. 기본으로 작성되는 값 그대로 두고 이벤트 이름도 적고 생성 버튼 클릭!

그리고 우리가 수정한 코드가 아직 배포가 되지 않았습니다. Deploy 버튼을 눌러서 배포해주세요.

이 상태가 되면 Test 버튼 클릭!

그럼 실행 결과가 새 탭으로 생깁니다. Response 에는 실행 결과가 뜨고 Function Logs에는 로그가 기록됩니다. 자세히 보니 event 에는 우리가 입력값으로 지정했던 것들이 출력되고 context는 메모리상 주소?가 출력되는 것 같습니다.

텔레그램 메시지를 보내는 코드를 작성하기 전에 환경변수에 토큰을 먼저 등록하겠습니다. 코드 파일에 토큰, 키, 등의 중요한 값을 적어두는 것은 보안상 큰 문제가 될 수 있습니다.

아래로 좀 내려보면 환경변수를 설정할 수 있는 공간이 있습니다. 편집 눌러주세요.

추가 버튼 누르고

발급받은 토큰을 입력하고 저장 버튼 눌러주세요.

그리고 작성했던 코드를 가져옵니다.

import http.client
import json
import os

def lambda_handler(event, context):
    # TODO implement
    
    print("event : ", event)
    print("context : ", context)

    TELEGRAM_API_HOST = 'api.telegram.org'
    TOKEN = os.environ['TOKEN']
    
    connection = http.client.HTTPSConnection(TELEGRAM_API_HOST)
    
    # 토큰과 메서명 지정
    url = f"/bot{TOKEN}/sendMessage"
    
    # HTTP 헤더
    headers = {'content-type': "application/json"}
    
    # 파라미터
    param = {
        'chat_id': 577386516,
        'text': 'python 에서 보냄'
    }
    
    # Http 요청
    connection.request("POST", url, json.dumps(param), headers)
    
    # 응답
    res = connection.getresponse()
    
    # Response body 출력
    print(json.dumps(json.loads(res.read().decode()), indent=4))
    print('응답코드 : ', res.status)
    print('메시지 : ', res.msg)
    
    # 연결 끊기
    connection.close()
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

TOKEN = os.environ['TOKEN'] 토큰 부분이 이렇게 바뀌었으니 참고해주세요. 환경 변수를 가져오는 코드입니다. 상단에 import os 도 해주세요

그리고 다시 디플로이 - 테스트 버튼 눌러주세요.

결과도 잘 나오고 봇에게 메시지도 잘 도착했습니다.

그 다음은 무엇을 해야하나?

챗봇은 사용자가 입력하면 그 입력값을 파악하여 응답을 해주는 방식으로 작동합니다. 그럼 '사용자가 입력하면' 이라는 조건을 만드는 부분이 람다에서는 트리거 입니다.

텔레그램에서는 Webhook 이라는 기능을 지원하는데요. 챗봇에게 메시지가 도착하면 지정한 URL로 요청을 보내줍니다. 우리는 람다가 실행될 수 있는 URL을 만들어주고 메시지가 올때마다 람다가 실행되게 하면 됩니다. 그 URL을 만드는 서비스가 AWS API Gateway 입니다.

API Gateway 생성하기

기존 탭을 놔두고 새 탭으로 열어주세요.
AWS API Gateway 링크

저는 이미 만들어본 API가 목록에 있네요. 우측 상단에 API 생성 눌러주세요.

챗봇은 간단한 HTTP 요청에 대한 응답 구현만 필요합니다. 첫 번째 HTTP API의 구축 버튼 눌러주세요

해당 API가 어떤 서비스와 연결되는지 선택하는 것이 통합 기능 입니다. 추후 S3 서비스 등 여러 AWS 서비스와 연결할 수 있습니다.

람다로 선택하면 아래에 함수를 선택할 수 있는 공간이 생깁니다. 사진처럼 설정을 맞춰주세요.

API 이름 고유할 필요 없다고 하니 알아볼 수 있게 작성하고 다음 버튼 눌러주세요.

웹훅은 Post 방식으로 지원합니다. API 참고 메서드를 POST로 바꿔주시고 경로도 마음대로 작성하셔도 됩니다. 다음 버튼 눌러주세요.

여러 개의 배포 구성을 할 수 있습니다. 그대로 놔두고 다음 버튼 눌러주세요.

검토하시고 생성 버튼 눌러주세요.

결과 화면입니다. 다시 람다 서비스 화면으로 가주세요.

트리거에 API 게이트웨이가 추가되어 있습니다. 클릭해주세요.

세부 정보를 눌러보면 API 엔드포인트가 있습니다. 이 URL에 POST요청이 들어오면 람다 서비스가 실행된다는 뜻입니다.

웹훅 등록하기

그럼 이 URL을 웹훅에 등록해보겠습니다.

사진 보니 감이 오시죠?

https://api.telegram.org/bot<토큰>/setWebhook?url=https://<각자 만든 Api Gateway 엔드포인트>
위 링크 만들어서 브라우저에 입력해도 됩니다.

결과로 아래 설명처럼 'Webhook was set' 메시지가 보이면 성공입니다. 이 메시지가 아니라면 토큰 잘 붙여넣기 하셨느지 살펴보세요.

테스트!

그 다음 봇에게 말을 걸면 자동으로 응답을 합니다.


다시 맨 처음 그림을 볼게요. 드디어 1번 부터 6번까지가 구현이 됐습니다!

Cloud Watch 서비스로 로그 보기

이제 의미있는 봇을 만들기 위해선 사용자의 입력을 보고 파악해서 입력에 맞는 응답을 해야겠죠?

람다가 실행된 로그를 보려면 Cloud Watch 서비스를 이용하면 됩니다.

Cloud Watch 링크

사진처럼 로그 그룹에 들어가보면 람다 서비스 로그 그룹이 있습니다. 클릭해주세요.

그럼 로그 스트림이 있는데요. 디플로이를 할 때마다 새로운 로그 스트림이 생깁니다.
제일 위에거 클릭해주세요.

그 다음 화살표를 눌러서 입력 값으로 무엇이 들어오는지 살펴볼게요.

이 입력값으로 테스트 이벤트를 만들려고 합니다. 그런데 테스트 입력값은 JSON 타입으로 되어있어야 합니다. 그런데 지금 로그에 찍힌 것은 ' 인데 JSON은 "로 되어있어야 인식이 됩니다. 이걸 변환해주는 사이트를 이용하겠습니다. 복사해주세요.

JSON Formatter 링크
여기로 이동해주세요. 그리고 앞에 'event :' 제외한 나머지 문자를 붙여넣기 하고 아래 Process 버튼 눌러주세요.

그럼 사진처럼 JSON으로 이쁘게 변환된 것을 볼 수 있습니다. 변환된 것을 다시 복사해서 테스트 이벤트 구성을 만들어주세요.

JSON 형식 지정 버튼 눌러주시고 생성 버튼 눌러주세요. 그럼 테스트 할 때마다 우리가 봇에게 '하이'라고 보낸 것 처럼 테스트를 할 수 있게 되었습니다.

우리가 '하이' 라고 보낸 메시지는 어떻게 찾을 수 있는지 보겠습니다.

body 키에 문자열로 되어있는 JSON 데이터들이 있습니다. 이것도 보기좋게 바꾸면

{
   "update_id":171035753,
   "message":{
      "message_id":28,
      "from":{
         "id":123456789,
         "is_bot":false,
         "first_name":"dragontiger",
         "username":"dragontiger_dev",
         "language_code":"ko"
      },
      "chat":{
         "id":123456789,
         "first_name":"dragontiger",
         "username":"dragontiger_dev",
         "type":"private"
      },
      "date":1613961338,
      "text":"\\ud558\\uc774"
   }
}

이렇게 되어있습니다. '하이' 라는 메시지는 body 키 안에 메시지 안에 message 키 안에 text 키의 값이 되겠습니다.

그럼 람다 코드를 이렇게 바꿔보고 디플로이 후 테스트 버튼 눌러주세요.

# HTTP 헤더
headers = {'content-type': "application/json"}

teleRes = json.loads(event['body'])

# 파라미터
param = {
    'chat_id': 577386516,
    'text': teleRes['message']['text']
}
    


버튼 옆에 화살표로 새로 만든 테스트가 되도록 이벤트를 바꿔주세요.

테스트가 잘 됐습니다. 이제 텔레그램 봇에게 다른 말을 걸어볼게요

보낸 메시지를 그대로 보여주네요!

이제 여러분들이 할 일은 입력한 문자열을 파악해서 '/' 문자로 시작하면 명령어로 인식하고 명령어에 대한 응답 설정하고 명령어가 아니면 응답을 하지 않도록 설정하면 간단한 챗봇이 완성됩니다.

긴 글 읽어주셔서 감사합니다.

profile
안녕하세요. 백엔드 엔지니어 입니다.

0개의 댓글