텔레그램 봇 추가

  1. 봇의 아버님과 면담을 요청한다.(퍽!) 방법은 검색창에 @BotFather를 입력하고 친구추가를 한다.
  2. 봇을 추가하기 위해 /newbot을 입력한다.
  3. 봇의 이름을 입력한다. (디스플레이에 사용되는 이름이다)
  4. 봇의 사용자명을 입력한다. 마지막이 bot 혹은 Bot으로 끝나야한다. (봇 ID. 검색, 봇의 경로명에 사용된다)
  5. 봇이 생성되면, 아래와 같은 부분에 출력되는 텔레그램 봇 토큰을 잘 메모해둔다.
    Use this token to access the HTTP API:
    ......
    


텔레그램에서 메세지 보내기


이제 준비 작업은 모두 끝났다. 텔레그램에서 메세지를 보내려면, 어떤 수단을 사용해서든지(프로그래밍 언어, IFTTT 등) 아래 옵션으로 HTTP 요청을 보내면 된다. <텔레그램 봇 토큰>에는 5번에서 적어둔 내용이, <챗 ID>에는 메세지 수신시에 얻어지는 사용자 챗 ID가 들어간다. (따옴표 없음에 주의)

URL : https://api.telegram.org/bot<텔레그램 봇 토큰>/SendMessage
Method : POST
Content : application/json
Body : {"chat_id": <챗 ID>, "text": "<보낼 메세지>"}


,

텔레그램 봇과 명령과 응답을 양방향으로 주고 받는 방법을 구현해보겠습니다. (Python 3 사용)

  • 텔레그램 봇을 구현하는 방법에는 두가지가 있는데, 그 중 한가지는 웹훅을 사용하는 방식으로 고정된 주소를 가진 사용자 서버를 등록시켜놓고 이를 통해 서비스하는 방법인데 아무래도 개인 사용자가 하기에는 부담이 따르겠죠.
  • 다른 한가지는 Long-Polling을 사용하는 방식인데, 서버를 열어놓지 않고 새로운 메시지가 있는지 여부를 약간 긴 주기로 계속적으로 확인하는 방법으로, 고정된 주소가 없는 개인 PC로도 서비스할 수 있는 장점이 있습니다. 여기서는 Long-Polling을 사용한 방식을 정리해보겠습니다.

다음은 Long-Polling에 의한 요청 처리가 구현되어, 기본적인 명령과 응답을 주고받는 기능이 동작하는 예제입니다.

  • 입력된 요청 문자열을 텔레그램 봇 창에 메세지로 보내는 내용이 구현되어있습니다.
  • 앱 상에서 요청 입력은 채널 포스팅에서 사용되었던 채널창이 아닌 텔레그램 봇 자체창에서 입력되어야 합니다

소스 중 <텔레그램 봇 토큰>은 아래 글을 참고하여 적당한 값으로 바꿔줍니다.

 

>> 텔레그램 봇 생성 및 봇 토큰 얻기

 

telegramagent.py :

# -*- coding: utf-8 -*-
  
import http.client, urllib
import json
import datetime
  
class ChatInfo:
  
  def __init__(self):
    self.chatId = None
    self.firstName = None
    self.lastName= None
    self.userName = None
    self.text = None
    self.date = None
  
class TelegramAgent:
  
  API_URL = "api.telegram.org"
  CONTENT_TYPE = "Content-type"
  APPLICATION_JSON = "application/json"
  
  BOT_PATH = "/bot%s"
  SEND_MESSAGE_PATH = "/sendMessage"
  SEND_MESSAGE_DATA = '{"chat_id": %s, "text": "%s"}'
  GET_ME_PATH = "/getMe"
  GET_UPDATES_PATH = "/getUpdates"
  GET_UPDATES_DATA = '{"offset": "%d"}'
  SEND_PHOTO_PATH = "/sendPhoto"
  SEND_PHOTO_DATA = '{"chat_id": %s, "photo": "%s"}'
  
  def __init__(self):
    self.bContinue = True
    self.response = None
    self.callback = None
    self.bDumpData = False
  
  def setToken(self, token):
    self.token = token
  
  def setCallback(self, callback):
    self.callback = callback
  
  def setContinue(self, bContinue):
    self.bContinue = bContinue
  
  def setDumpData(self, bDumpData):
    self.bDumpData = bDumpData
  
  def convertDate(self, date, format):
    return datetime.datetime.fromtimestamp(date).strftime(format)
  
  def packChatId(self, chatId):
    if len(chatId) > 0 and chatId[:1] == "@":
      return "\"%s\"" % chatId
    return chatId
  
  def postRequest(self, url, path, headers, paramsRaw):
    try:
      params = paramsRaw
      conn = http.client.HTTPSConnection(url)
      conn.request("POST", path, params.encode("UTF-8"), headers)
      self.response = conn.getresponse()
      self.data = self.response.read().decode("UTF-8")
      if self.bDumpData:
        print(self.data)
      conn.close()
    except (ConnectionError, TimeoutError) as e:
      print(f'Catched : {e}')
  
  def sendCommon(self, actionPath, data):
    path = self.BOT_PATH % self.token + actionPath
    self.postRequest(self.API_URL, path,
        {self.CONTENT_TYPE : self.APPLICATION_JSON}, data);
  
  def sendNoData(self, actionPath):
    self.sendCommon(actionPath, "")
  
  def sendMessage(self, chatId, message):
    chatId = self.packChatId(chatId)
    data = self.SEND_MESSAGE_DATA % (chatId, message)
    self.sendCommon(self.SEND_MESSAGE_PATH, data)
  
  def sendPhoto(self, chatId, photoUrl):
    data = self.SEND_PHOTO_DATA % (chatId, photoUrl)
    self.sendCommon(self.SEND_PHOTO_PATH, data)
  
  def getMe(self):
    self.sendNoData(self.GET_ME_PATH)
  
  def getUpdates(self, offset):
    data = self.GET_UPDATES_DATA % offset
    self.sendCommon(self.GET_UPDATES_PATH, data)
  
  def processResult(self, result):
    chatInfo = ChatInfo()
  
    try:
      message = result['message']
      if message == None:
        print("message is null")
      else:
        chat = message['chat']
        if chat == None:
          print("chat is null")
        else:
          chatInfo.chatId = "%d" % chat['id']
          chatInfo.firstName = chat['first_name']
          chatInfo.lastName = chat['last_name']
          chatInfo.userName = None
          if 'username' in chat:  # username 키 값 존재 체크하도록. 설정 안하면 없을 수 있음
            chatInfo.userName = chat['username']
        chatInfo.text = message['text']
        chatInfo.date = message['date']
          
      if self.callback != None:
        self.callback(chatInfo)
    except KeyError as e:
      return
  
  def loop(self):
    self.bContinue = True
    updateId = 0
      
    while (self.bContinue):
      self.getUpdates(updateId)
      if self.response.status == 200:
        respDict = json.loads(self.data)
        results = respDict['result']
        resultsNum = len(results)
        if resultsNum > 0:
          lastResult = results[resultsNum - 1]
          updateId = lastResult['update_id'] + 1
          self.processResult(lastResult)

main.py :

# -*- coding: utf-8 -*-
  
from telegramagent import *
 
def myCallback(chatInfo):
  print("chat id = %s" % chatInfo.chatId)
  print("username = %s" % chatInfo.userName)
  print("date = %s" % agent.convertDate(chatInfo.date, '%Y-%m-%d %H:%M:%S'))
  print("text = %s" % chatInfo.text)
  if chatInfo.text == "quit":
    agent.setContinue(False)
  else:
    agent.sendMessage(chatInfo.chatId, "입력된 요청 : %s" % chatInfo.text)
 
agent = TelegramAgent()
agent.setDumpData(True)
agent.setToken("<텔레그램 봇 토큰>")
agent.setCallback(myCallback)
  
agent.loop()

telegramagent.py 주요 행 설명

  • 7 : 요청을 보낸 사용자 정보가 담기는 클래스입니다.
  • 77 : chatId가 채널 ID일 경우 따옴표로 감싸줍니다.
  • 133 : Long-Polling 시, update id를 0으로 설정할 경우, 기존에 가지고 있는 상태 데이터를 모두 가져오고, 두번째 호출부터는 마지막 update id + 1로 설정하면 기존의 데이터는 무시하고 새로운 데이터만 가져오게 됩니다.

main.py 주요 행 설명

  • 11 : quit 명령을 받으면 서버를 종료합니다.
  • 13 : Callback 함수에서 입력된 요청 문자열을 텔레그램 봇 창에 메세지로 보내줍니다.
  • 16 : 텔레그램 서버에서 받은 응답 정보를 출력합니다. 실사용시는 False로 합니다.
  • 18 : 요청을 처리할 Callback 함수를 등록하여 사용합니다.

 

 

정찰 위성 이미지

 

 

 

참고 사이트

  • 더 많은 텔레그램 봇 라이브러리들 : https://core.telegram.org/bots/samples

 

 

<테스트 환경> 
- OS : Windows 10 
- Python 버전 : 3.6 

 

,

다음은 몇가지 부가적인 기능이 추가된 예제입니다.

  • getMe : 텔레그램 봇 자신에 대한 정보를 가져온다.
  • getUpdates : 텔레그램 봇의 상태 정보를 가져온다.
  • sendPhoto : 텔레그램 봇 채널에 이미지를 전송한다.
# -*- coding: utf-8 -*-

import http.client, urllib
import json
import datetime

class TelegramAgent:

  API_URL = "api.telegram.org"
  CONTENT_TYPE = "Content-type"
  APPLICATION_JSON = "application/json"

  BOT_PATH = "/bot%s"
  SEND_MESSAGE_PATH = "/sendMessage"
  SEND_MESSAGE_DATA = '{"chat_id": %s, "text": "%s"}'
  GET_ME_PATH = "/getMe"
  GET_UPDATES_PATH = "/getUpdates"
  GET_UPDATES_DATA = '{"offset": "%d"}'
  SEND_PHOTO_PATH = "/sendPhoto"
  SEND_PHOTO_DATA = '{"chat_id": %s, "photo": "%s"}'

  def __init__(self):
    self.bContinue = True
    self.response = None
    self.callback = None
    self.bDumpData = False

  def setToken(self, token):
    self.token = token

  def setCallback(self, callback):
    self.callback = callback

  def setContinue(self, bContinue):
    self.bContinue = bContinue

  def setDumpData(self, bDumpData):
    self.bDumpData = bDumpData

  def convertDate(self, date, format):
    return datetime.datetime.fromtimestamp(date).strftime(format)

  def packChatId(self, chatId):
    if len(chatId) > 0 and chatId[:1] == "@":
      return "\"%s\"" % chatId
    return chatId

  def postRequest(self, url, path, headers, paramsRaw):
    params = paramsRaw
    conn = http.client.HTTPSConnection(url)
    conn.request("POST", path, params.encode("UTF-8"), headers)
    self.response = conn.getresponse()
    self.data = self.response.read().decode("UTF-8")
    if self.bDumpData:
      print(self.data)
    conn.close()

  def sendCommon(self, actionPath, data):
    path = self.BOT_PATH % self.token + actionPath
    self.postRequest(self.API_URL, path, 
        {self.CONTENT_TYPE : self.APPLICATION_JSON}, data);

  def sendNoData(self, actionPath):
    self.sendCommon(actionPath, "")

  def sendMessage(self, chatId, message):
    chatId = self.packChatId(chatId)
    data = self.SEND_MESSAGE_DATA % (chatId, message)
    self.sendCommon(self.SEND_MESSAGE_PATH, data)

  def sendPhoto(self, chatId, photoUrl):
    chatId = self.packChatId(chatId)
    data = self.SEND_PHOTO_DATA % (chatId, photoUrl)
    self.sendCommon(self.SEND_PHOTO_PATH, data)

  def getMe(self):
    self.sendNoData(self.GET_ME_PATH)

  def getUpdates(self, offset):
    data = self.GET_UPDATES_DATA % offset
    self.sendCommon(self.GET_UPDATES_PATH, data)

agent = TelegramAgent()
agent.setDumpData(True)
agent.setToken("<텔레그램 봇 토큰>")
agent.getMe()
agent.getUpdates(0)
agent.sendPhoto("@<채널 ID>", "http://t1.daumcdn.net/thumb/R0x640/?fname=http%3A%2F%2Fcfile168.uf.daum.net%2Fimage%2F235D1A445731A2332F780E")

<테스트 환경>
- OS : Windows 7
- Python 버전 : 3.6


,

앞서 작성한 코드를 클래스로 정리해 보겠습니다.


# -*- coding: utf-8 -*-

import http.client, urllib
import json
import datetime

class TelegramAgent:

  API_URL = "api.telegram.org"
  CONTENT_TYPE = "Content-type"
  APPLICATION_JSON = "application/json"

  BOT_PATH = "/bot%s"
  SEND_MESSAGE_PATH = "/sendMessage"
  SEND_MESSAGE_DATA = '{"chat_id": %s, "text": "%s"}'

  def __init__(self):
    self.bContinue = True
    self.response = None
    self.callback = None
    self.bDumpData = False

  def setToken(self, token):
    self.token = token

  def setCallback(self, callback):
    self.callback = callback

  def setContinue(self, bContinue):
    self.bContinue = bContinue

  def setDumpData(self, bDumpData):
    self.bDumpData = bDumpData

  def packChatId(self, chatId):
    if len(chatId) > 0 and chatId[:1] == "@":
      return "\"%s\"" % chatId
    return chatId

  def postRequest(self, url, path, headers, paramsRaw):
    params = paramsRaw
    conn = http.client.HTTPSConnection(url)
    conn.request("POST", path, params.encode("UTF-8"), headers)
    self.response = conn.getresponse()
    self.data = self.response.read().decode("UTF-8")
    if self.bDumpData:
      print(self.data)
    conn.close()

  def sendCommon(self, actionPath, data):
    path = self.BOT_PATH % self.token + actionPath
    self.postRequest(self.API_URL, path, 
        {self.CONTENT_TYPE : self.APPLICATION_JSON}, data);

  def sendNoData(self, actionPath):
    self.sendCommon(actionPath, "")

  def sendMessage(self, chatId, message):
    chatId = self.packChatId(chatId)
    data = self.SEND_MESSAGE_DATA % (chatId, message)
    self.sendCommon(self.SEND_MESSAGE_PATH, data)

agent = TelegramAgent()
agent.setDumpData(True)
agent.setToken("<텔레그램 봇 토큰>")
agent.sendMessage("@<채널 ID>", "test string [한글]")


<테스트 환경>
- OS : Windows 7
- Python 버전 : 3.6


,

위의 두 글을 참고하면 웹페이지의 특정 class에 해당하는 부분을 추출하여 텔레그램 채널로 보내는 것이 가능하다.


사전 준비사항

  1. app template을 사용하여 프로젝트를 생성한다.

    lein new app clj-test-02
    

  2. project.clj 파일의 dependencies 부분에 아래와 같이 필요한 모듈을 정의한다.

    (defproject clj-test-02 "0.1.0-SNAPSHOT"
      :description "FIXME: write description"
      :url "http://example.com/FIXME"
      :license {:name "Eclipse Public License"
                :url "http://www.eclipse.org/legal/epl-v10.html"}
      :dependencies [[org.clojure/clojure "1.8.0"][clj-http "2.2.0"] [enlive "1.1.6" :exclusions [org.clojure/clojure]]]
      :main ^:skip-aot clj-test-02.core
      :target-path "target/%s"
      :profiles {:uberjar {:aot :all}})
    
    

예제

(ns clj-test-02.core
 (:gen-class)
 (:require [net.cgrand.enlive-html :as html])
 (:require [clj-http.client :as client]))

(use 'clojure.pprint)

(defn html-data []
  ;; clj-html 모듈을 사용하여 웹페이지의 내용을 가져온다.

  (html/html-resource (java.io.StringReader.
      (:body (client/get "http://httpbin.org"))))
)

(defn -main [& args]
  
  (pprint (client/post "https://api.telegram.org//bot<텔레그램 봇 토큰>/sendMessage" 
    {:body (format "{\"chat_id\": \"@<채널 ID>\", \"text\": \"%s\"}"
             (first (:content (first (html/select (html-data) [:.bash])))))
     :headers {"Content-Type" "application/json"}}))
)


<테스트 환경>
- OS : Windows 7
- Leiningen 버전 : 1.0.0
,

위의 두 글을 참고하면 웹페이지의 특정 class에 해당하는 부분을 추출하여 텔레그램 채널로 보내는 것이 가능하다. (두둥)


Python 2 :
# -*- coding: utf-8 -*-
  
import requests
from bs4 import BeautifulSoup
    
r = requests.get('http://httpbin.org')
   
# beautiful soup 초기화
soup = BeautifulSoup(r.text, "html.parser")
   
# class로 찾기
mr = soup.find(class_="bash")
result = mr.get_text()
result_utf8 = result.encode("UTF-8")
  
# 텔레그램 채널로 전송
url = "https://api.telegram.org/bot<텔레그램 봇 토큰>/sendMessage"
headers = {"Content-type": "application/json"}
params = '{"chat_id": "@<채널 ID>", "text": "텍스트 추출 : %s"}' % result_utf8
r = requests.post(url, headers = headers, data = params)
   
print(r.text)

Python 3 :
# -*- coding: utf-8 -*-
  
import requests
from bs4 import BeautifulSoup
    
r = requests.get('http://httpbin.org')
   
# beautiful soup 초기화
soup = BeautifulSoup(r.text, "html.parser")
   
# class로 찾기
mr = soup.find(class_="bash")
result = mr.get_text()

# 텔레그램 채널로 전송
url = "https://api.telegram.org/bot<텔레그램 봇 토큰>/sendMessage"
headers = {"Content-type": "application/json"}
params = '{"chat_id": "@<채널 ID>", "text": "텍스트 추출 : %s"}' % result
r = requests.post(url, headers = headers, data = params.encode("UTF-8"))
   
print(r.text)


테스트 환경
 - OS : Windows 7
 - Python 버전 : 2.7, 3.6


,

사전 준비 사항


clj-http 모듈 사용을 위해서 project.clj 파일의 dependencies 부분에 아래와 같이 추가해준다. (버전은 참고사이트에서 최신 버전을 확인하자)

(defproject projectname "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                  [clj-http "2.2.0"]])


채널 포스팅


Clojure를 사용하여 텔레그램 채널에 메세지를 보내는 예제입니다.


<텔레그램 봇 토큰>과 <채널 ID> 부분은 아래 글에서 설명했던 값으로 바꿔줍니다.


–> 텔레그램 봇으로 채널 포스팅 - 1. 준비 작업

(require '[clj-http.client :as client])

(println (client/post "https://api.telegram.org//bot<텔레그램 봇 토큰>/sendMessage"
  {:body "{\"chat_id\": \"@<채널 ID>\", \"text\": \"test string\"}"
            :headers {"Content-Type" "application/json"}}))
(println "done")


참고 글


참고 사이트


<테스트 버전>

Eclipse 버전 : Mars
CCW 버전 : 0.35.0.STABLE001
Leiningen 버전 : 1.0.0


,

파이썬을 사용하여 텔레그램 채널에 메세지를 보내는 예제입니다.
<텔레그램 봇 토큰>과 <채널 ID> 부분은 아래 글에서 설명했던 값으로 바꿔줍니다.


–> 텔레그램 봇으로 채널 포스팅 - 1. 준비 작업


Python 2 :
# -*- coding: utf-8 -*-

import requests
 
url = "https://api.telegram.org/bot<텔레그램 봇 토큰>/sendMessage"
headers = {"Content-type": "application/json"}
params = '{"chat_id": "@<채널 ID>", "text": "test string [한글]"}'
r = requests.post(url, headers = headers, data = params)
 
print(r.text)
print(r.status_code)

Python 3 :
# -*- coding: utf-8 -*-

import requests
 
url = "https://api.telegram.org/bot<텔레그램 봇 토큰>/sendMessage"
headers = {"Content-type": "application/json"}
params = '{"chat_id": "@<채널 ID>", "text": "test string [한글]"}'
r = requests.post(url, headers = headers, data = params.encode("UTF-8"))
 
print(r.text)
print(r.status_code)


참고 글


<테스트 환경>
 - OS : Windows 7
 - Python 버전 : 2.7, 3.6
,