텔레그램 봇 추가

  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


,

바이너리 생성

mvn package

실행

java -cp target/<파일명>.jar <패키지를 포함한 메인클래스명>

로컬 레파지토리에 업데이트

mvn install


,

파이썬에서 문자열은 바이트 코드와 유니코드로 나뉜다.


파이썬 3에서,

바이트 코드의 타입 -> <class 'bytes'>

유니 코드의 타입 -> <type 'str'>



변환


바이트 코드는 euc-kr, UTF–8 등과 같이 특정 인코딩이 적용된 문자열을 말한다. 파이썬에서 유니코드는 어떤 특정 인코딩에 속하지 않는 유니버셜한 어떤 것을 나타낸다. (다른 언어에서 흔히 UTF–8을 유니코드로 일컫는 것과는 다름에 유의)

  • 유니코드 >>> 바이트 코드로 변환하기 위해서는 encode() 함수가 사용된다.

  • 바이트 코드 >>> 유니코드로 변환하기 위해서는 decode() 함수가 사용된다.

    bstr = str.encode("UTF–8") # 유니코드에서 UTF–8 바이트 코드로 변환 
    str = bstr.decode("UTF–8") # UTF–8 바이트 코드에서 유니코드로 변환


타입 확인

  • type() 함수를 사용하여 어떤 문자열이 유니코드인지 바이트 코드인지 알아낼 수 있다.
    print(type(bstr)) # 바이트 코드일 경우 
    -> <type 'bytes'> 
    
    print(type(str)) # 유니코드일 경우
    -> <type 'str'>


소스 코드 상에서 대입시,


파이썬 2에서는 기본적으로 바이트 코드이고 <type 'str'>로 표기되며, 이에 대비되는 유니코드가 있는 것과 달리,

파이썬 3에서는 기본적으로 유니코드이고 <type 'str'>로 표기된다.


참고


print 함수를 사용하여 바이트 코드를 출력할 경우, 앞에 b'가 붙는 것을 볼 수 있다.


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


,

https://www.aigaming.com/


비트코인도 준다는데.. (응?)


상품이 HD Drone으로 변경되었네요..


,





Halite는 오픈 소스 인공 지능 프로그래밍 경연장으로, 사용자는 자신이 선택한 코딩 언어를 사용하여 봇을 움직이는 코드를 작성하여 2차원 가상보드 상에서 다른 사용자와 겨루게 된다.

  • 홈페이지 : https://halite.io/
  • 지원 OS : MacOS, Windows, Linux-x64
  • 지원 언어 : C++, CSharp, Clojure, Dart, Elixir, Go, Haskell, Java, JavaScript, Julia, Kotlin, ML-StarterBot-Python, OCaml, PHP, Python3, Ruby, Rust, Scala, Swift

게임 규칙


계정 생성

  • Halite를 플레이하기 위해서는 github 계정이 필요하다. 계정이 없다면 Halite 홈페이지에서 Sign up/Sign in 버튼을 누른 후, 계정을 생성한다. 주의할 점은 Username에 입력하는 내용이 실제로 게임내에서 자신을 나타내는데 사용되며 나중에 편집할 수 없다!

스타터킷 다운로드

스타터킷 편집

  • 스타터킷의 압축을 풀고 봇에 대한 소스 파일을 편집한다. 파이썬의 경우는 MyBot.py 파일을 편집하면 된다. 물론 수정 안하고 그대로 사용해도 된다!


오프라인 테스트

  • 수정한 스타터킷을 업로드하기 전에 오프라인에서 테스트해볼 수 있다. 윈도우의 경우는 run_game.bat를, 유닉스 계열의 경우는 run_game.sh를 실행하면 된다.

스타터킷 업로드

  • 스타터킷 폴더에서, 봇에 대한 소스 파일과 hlt 폴더 두가지를 압축 파일 최상위에 오도록 zip 압축을 한다.
  • Halite 홈페이지 상단의 Submit a bot을 누른 뒤, 왼쪽에 Select a zip 버튼을 눌러 스타터킷을 업로드할 수 있다.
  • 업로드한 후, 소스 코드가 컴파일 되는데 일정 시간이 소요되며, 컴파일이 완료되면, 주기적으로 랜덤한 상대와 자동적으로 대결이 이루어지게 된다.


전적 확인

  • 홈페이지 상단에서 플레이어 아이콘을 누르고, View Profile을 선택하면 그동안의 대결 히스토리를 확인할 수 있다. 시간 표시 부분을 누르면 그래픽과 함께 확인할 수 있다.


리플레이 파일 확인

  • Halite 홈페이지 상단의 Submit a bot을 누른 뒤, 왼쪽에 Select file 버튼을 눌러 오프라인 테스트시 생성된 리플레이 파일(.hlt)을 올려 그래픽과 함께 확인할 수 있다.

참고 자료


,