[Telegram Bot] lol api 이용한 게임 알림 챗봇 제작기

라이엇 게임즈는 공식 홈페이지에서 롤 api를 통해 게임 정보와 유저 정보를 제공한다. 

https://developer.riotgames.com/

 

Riot Developer Portal

About the Riot Games API With this site we hope to provide the League of Legends developer community with access to game data in a secure and reliable way. This is just part of our ongoing effort to respond to players' and developers' requests for data and

developer.riotgames.com

라이엇 계정이 있다면 개발자로 등록을 해서 개발 키(Development API KEY)를 발급받아서 자신만의 서비스에 결합할 수 있다. 물론 테스트 목적이라면 24시간 동안만 유효한 테스트용 API KEY를 이용해서 원하는 기능을 테스트해볼 수 있다.

정식 개발 키를 발급받기 위해서는 신청과 승인 과정이 필요하며 이 과정에 꽤 오랜 시간이 소요된다. 신청하는 이유 또한 최대한 자세하게 적는 것이 좋으며, 2~3주 정도 기다린 후 홈페이지에서 발급 여부를 확인할 수 있다. 필자는 발급이 될 때까지 24시간마다 갱신이 필요한 API키를 이용하여 개발을 하고, 실운영은 정식 키를 사용하였다.

테스트 키 발급
지원하는 API 목록

  적절하게 API 키를 발급 받았다면 원하는 기능인 게임 알림 봇을 위해 API를 찾는다. API의 구조는 어느 공공 API를 사용하는 것과 같다. Request Header에 필요한 API-KEY와 필요한 정보를 채운 후 원하는 리소스가 있는 주소에 요청을 하면 반환값으로 데이터를 전달받을 수 있다. 데이터가 적절하게 넘어왔는지는 HTTP 상태 코드를 통해 확인할 수 있다. 응답은 모두 json 포맷으로 받을 수 있고 사용자가 파싱하여 원하는 데이터를 가져다가 쓰면 된다.

소환사 명 

먼저 일반적으로 알고 있는 소환사명과 API에서 사용자를 식별하는 방법이 다르기 때문에 원하는 형태로 변경해 주어야 한다. 

https://developer.riotgames.com/apis#summoner-v4/GET_getBySummonerName

API를 통해 소환사명을 전달하면, 그 응답으로 accountId와 암호화된 id puuid 등 필요한 데이터를 받을 수 있다. 코드는 아래와 같이 작성하였다.

import requests, json
import urllib.request
import urllib.parse
import datetime

BASE_URL = "https://kr.api.riotgames.com"
TOKEN = "RGAPI-00000000000000000000000" # 여기에 발급받은 키 입력
HEADERS = {'User_Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" \
                         "Chrome/80.0.3987.132 Safari/537.36"}


class LOL:
    def __init__(self, summonname):
        self.puid = ''
        self.userlevel = ''
        self.summonname = summonname

        with open('/home/gomguk/champion.json', "r", encoding = 'cp949') as f:
            f = json.load(f)
        self.champ = {}
        keys = [key for key in f['data']]
        for i in keys:
            self.champ[i] = int(f['data'][i]['key'])

    def summoners(self, summonname=None):
        if (summonname == None):
            summonname = self.summonname
        encode_name = urllib.parse.quote_plus(summonname)
        url = BASE_URL + '/lol/summoner/v4/summoners/by-name/' + encode_name
        params = {'api_key': TOKEN}
        try:
            response = requests.get(url, params=params)
            self.id = response.json()["id"]
            self.userlevel = response.json()["summonerLevel"]
        except requests.exceptions.HTTPError as err:
            return err

여기서 주의할 점은 데이터가 URL을 통해 전송되기 때문에 소환사 명에 공백이 있는 경우 제대로 전달되지 않을 수 있다. 따라서 urlib.parse.quote_plus()를 통해서 공백을 +로 치환해 주었다. 이 코드를 통해 클래스 내의 id 변수에 소환사의 id 정보를 담았다.

"Hide on bush"에 대한 응답

티어 정보

게임 알림 봇도 좋지만, 명령을 통해 친구의 티어를 실시간으로 알고 싶을 때가 있다. 

https://kr.api.riotgames.com/lol/league/v4/entries/by-summoner/{id}
    def gettier(self, uid=None):
        # TODO [0] 자유랭크, [1] 솔로랭크
        url = BASE_URL + '/lol/league/v4/entries/by-summoner/' + self.id
        params = {'api_key': TOKEN}
        try:
            response = requests.get(url, params=params)
            return (f"{response.json()[0]['summonerName']} 님 lv.{self.userlevel}\n"
                    f"솔로랭크: {response.json()[0]['tier']} {response.json()[0]['rank']} {response.json()[0]['leaguePoints']}점\n"
                    f"                     {response.json()[0]['wins']}승 {response.json()[0]['losses']}패\n"
                    f"자유랭크: {response.json()[1]['tier']} {response.json()[1]['rank']} {response.json()[1]['leaguePoints']}점\n"
                    f"                     {response.json()[1]['wins']}승 {response.json()[1]['losses']}패\n")

        except requests.exceptions.HTTPError as err:
            return err
        except IndexError:
            return (f"소환사를 찾을 수 없거나 랭크 전적이 없어요!")

티어를 가져왔다!

실시간 게임정보

게임 알림봇을 만들기 위해서는 일정 시간마다 그 사람이 게임중인지 아닌지를 검사해야 한다. 이 말은 일정 주기를 가지고 API를 직접 호출하여 응답을 분석해서 원하는 값이 있는지를 검사해야 한다는것이다.

https://kr.api.riotgames.com/lol/spectator/v4/active-games/by-summoner/{uid}
def nowgame(self, uid=None):
        if uid == None:
            uid = self.id
        url = BASE_URL + '/lol/spectator/v4/active-games/by-summoner/' + uid
        params = {'api_key': TOKEN}
        try:
            response = requests.get(url, params=params)
            if response.json()["gameMode"] =="CLASSIC":
                gamemode = "소환사의 협곡"
            elif response.json()["gameMode"] =="ARAM":
                gamemode = "칼바람 나락"
            elif response.json()["gameMode"] =="URF":
                gamemode = "우르프"
            elif response.json()["gameMode"] =="SIEGE":
                gamemode = "돌격 넥서스"
            elif response.json()["gameMode"] == "ONEFORALL":
                gamemode = "단일챔피언"
            elif response.json()["gameMode"] == "ARSR":
                gamemode = "All Random Summoner's Rift games"
            else:
                gamemode = response.json()["gameMode"]

            if response.json()["gameType"] == "MATCHED_GAME":
                if response.json()["gameQueueConfigId"] == 420:
                    gametype = "솔로 랭크"
                elif response.json()["gameQueueConfigId"] == 430:
                    gametype = "일반 게임"
                elif response.json()["gameQueueConfigId"] == 100:
                    gametype = "칼바람 나락"
                elif response.json()["gameQueueConfigId"] in(1090, 1100, 1110):
                    gametype = "롤토체스"
                else:
                    gametype = "자유 랭크"

            elif response.json()["gameType"] == "CUSTOM_GAME":
                gametype = "사용자 설정 게임"
            gametime = str(datetime.timedelta(seconds=response.json()["gameLength"]))

            for i in range(10):
                sumon = response.json()["participants"][i]
                if sumon["summonerName"] == self.summonname:
                    chmpId = sumon["championId"]
                    for ch, k in self.champ.items():
                        if k == chmpId:
                            chmp = ch
                    break
                else:
                    chmp = "알수없음"
            return (f"\t<{self.summonname}>\n"
                    f"{gamemode}  {gametype}  {gametime}\n"
                    f"챔피언 >>\t\t{chmp}")

        except requests.exceptions.HTTPError as err:
            return (f"현재 게임중인 소환사가 없어요!")
        except KeyError:
            return (f"현재 게임중인 소환사가 없어요!")

Response Body 중 일부

응답의 키 값을 보면 무슨 값들을 나타내고 있는지 이해할 수 있다. 필요한 정보들을 가공해서 결과로 정리하면 될 것 같다.

코드에서 사용한 게임정보와 맵 정보는 라이엇 홈페이지에서 제공하고 있다.

https://developer.riotgames.com/docs/lol

 

Riot Developer Portal

League of Legends Overview This set of documentation will help you gain a better understanding of how to use the APIs and developer resources related to League of Legends. It is designed to help you begin developing your own tools and products for the Leag

developer.riotgames.com

이제 검색할 소환사명을 등록한 후 LOL.nowgame()을 주기적으로 호출해주면 그사람이 게임중인지 아닌지 확인할 수 있다. 하지만 라이엇에서는 너무 잦은 API 호출을 막기 위해 요청 제한을 걸어두었다.

요청 수를 초과하면 응답을 받을 수 없다

위의 조건을 명심하면서 적절하게 요청하도록 한다. 

    def crongame(self):
        self.idlist = ['소환사1', '소환사2', '소환사3', '소환사4', '소환사5', '소환사6', '소환사7', '소환사8']
        allset = ''
        for i in self.idlist:
            self.summonname = i
            chk = self.nowgame(self.summoners(i))
            if(chk == "현재 게임중인 소환사가 없어요!"):
                continue
            else:
                allset = allset + chk + "\n-----------------\n"
        if(allset == ''):
            return 0

        return f"지금 게임중인 소환사!\n{allset}"

주기적인 호출을 위해 cron 모듈을 사용할 것이고 호출할 함수인 crongame을 미리 정의해 두었다. crontab의 주기는 매 분 호출을 통해 새로운 게임인지 여부를 판단해서 알림의 띄워줄 수도 있지만, 서버가 그렇게 좋지도 않을 뿐더러 그렇게 중요한 정보가 아니기 때문에 20분 주기로 호출하면서 게임중인 사람이 있는지 여부를 확인하도록 했다.

자동게임검색!

봇으로 데이터 받기

텔레그램 봇 API를 이용하여 명령어를 등록하고, 데이터를 받을 수 있다.

class Telebot:
    def __init__(self):
        self.my_token = my_token
        
    def lol(self, update, context):
        if context.args[0] is "help":
            context.bot.send_message(chat_id=update.effective_message.chat_id, text=f"롤 티어 검색\n/lol 소환사명")
            return
        else:
            a = LOL(context.args[0])
            a.summoners()
            context.bot.send_message(chat_id=update.effective_message.chat_id,
                                     text=f"<티어 검색>\n{a.gettier()}")

    def nowall(self, update, context):
        a = LOL("Hide on bush")
        b = a.allgame()
        context.bot.send_message(chat_id=update.effective_message.chat_id,text=f"<게임 검색>\n{b}")

    def main(self):
        updater = Updater(self.my_token, use_context=True)
        dp = updater.dispatcher

        dp.add_handler(CommandHandler('lol', self.lol, pass_args=True))
        dp.add_handler(CommandHandler('nowall', self.nowall))

명령어를 통해 언제든지 확인 가능

후기

간단하게 작성한 스크립트지만 모든 기능이 속도도 느리지 않고 문제 없이 잘 동작한다. 물론 실제 게임 시간과 지연 없이 거의 비슷하게 일치한다. 공식 API를 지원하지 않았더라면 전적 사이트를 크롤링하거나 클라이언트를 후킹해야 했을 것이다. 다른 분들은 전적 데이터를 모아 학습시킨 후 실시간 랭크 승률 예측 까지 하는 것을 보았다. 본인의 역량과 시간이 허락한다면 충분히 해볼만한 프로젝트인 것 같다.

반응형