GAE/P+Tweepy+RIOT APIでTwitter BOTサービスを作る！(前編)

##はじめに
　Google App Engine/Pythonの練習として、League of Legendsというゲームの戦績結果を自動でTwitterに投稿するBOTサービスを作ります。Twitter認証はOAuth対応のTweepyを使用して、誰でも登録できるサービスにします。ゲームの戦績は運営公式のRIOT APIから取得します。ソースコードは[GitBucket](https://bitbucket.org/orfx/tweetoflegends)に、完成品は[こちら](http://tol.orfx.jp/)で運用してます。

##Google App EngineのHello World
　GAE/Pのアプリケーション設定はyamlという言語で記述していきます。app.yamlはAndroidでいうAndroidManifest.xmlの様なアプリケーション全体の設定を記述するファイルです。また、Djangoでいうurls.pyの様なURLルーティングの設定も記述します。サーバで実際に稼働させるアプリケーションのバージョンは容易に切り替えられるため、デプロイ時はこまめにバージョンを増やすとトラブル時に慌てずに済みます。

```app.yaml
application: example_app #アプリケーション名
version: 1
runtime: python27 #Python2.7を使用
api_version: 1
threadsafe: true

handlers: #URLのルーティングを記述
- url: /hell
  script: hell.app #hell.pyの変数appを呼び出す
```

　今回はwebapp2を使ってアプリケーションをPythonで実装していきます。

```hell.py
#! -*- coding: utf-8 -*-
import webapp2

class hellHandler(webapp2.RequestHandler): #処理を引き渡されるハンドラ
    def get(self): #GETメソッドの処理
        self.response.out.write('Hell World!')
    def post(self): #POSTメソッドの処理
        self.response.out.write('Postes Hell World!')

app = webapp2.WSGIApplication([ ('/hell', hellHandler) ]) #handlers:で指定したパス・変数からハンドラを呼び出す
```

　```http://example_app.appspot.com/hell```へアクセスするとHell Worldが表示されるはずです。

##TweepyをGAEで動作させる
　GAEでTweepyを使用するには自分で環境を用意する必要があります。まず、[GitHub](https://github.com/tweepy/tweepy)からtweepyフォルダを丸ごとダウンロードし、自分のプロジェクトファルダ内に配置します。次に、Tweepyはsslを使用するのでapp.yamlに使用するライブラリを追記します。また、コールバック用アドレスを指定してセキュリティ確保のためHTTPS通信を強制させます。

```app.yaml
application: example_app
version: 1
runtime: python27
api_version: 1
threadsafe: true

libraries: #使用ライブラリを指定
- name: ssl
  version: latest

handlers:
- url: /
  script: main.app
  
- url: /callback #コールバック用アドレス
  script: main.app
  secure: always #HTTPS通信を強制
```

　次に、ユーザにOAuth認証させてアクセストークンキーとアクセスシークレットトークンキーをデータストア(GAEのデータベース)に格納するテストアプリを実装してみましょう。データストアは一般的なRDBMSとは設計が異なりますが、DjangoライクなO/Rマッパが用意されているので何となくモデルを使う分にはあまり苦労しません。

```main.py
#! -*- coding: utf-8 -*-
from google.appengine.ext import db

import webapp2, tweepy

#Twitterコンシュマーキー
CONSUMER_KEY = '********************'
CONSUMER_SECRET = '**************************************'

class modelUser(db.Model): #Twitterアカウント情報格納用のモデル定義
    twitter_name = db.StringProperty()
    access_key = db.StringProperty()
    access_secret = db.StringProperty()
    date = db.DateTimeProperty(auto_now_add=True)

class mainHandler(webapp2.RequestHandler):
    def get(self):
        #認証開始ボタンを出力
        self.response.out.write('<form method="POST" action="./"><button type="submit">認証</button></form>')
    def post(self):
        #Tweepyにコンシュマーキーをセット
        auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
        try:
            redirect_url = auth.get_authorization_url() #OAuth認証用URLを取得
            self.redirect(redirect_url) #OAuth認証用URLにリダイレクト
        except Exception as e:
            self.response.out.write('エラ')

class callbackHandler(webapp2.RequestHandler):
    def get(self):
        auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
        try:
            #なんか受け取ったパラメタをTweepyに色々と認証させる
            auth.set_request_token(self.request.get('oauth_token'), self.request.get('oauth_verifier'))
            auth.get_access_token(self.request.get('oauth_verifier'))
            access_key = auth.access_token.key
            access_secret = auth.access_token.secret
            auth.set_access_token(access_key, access_secret)
            api = tweepy.API(auth) #以降、このオブジェクトから色々情報を取得できる
            
            modeluser = modelUser().get_or_insert(str(api.me().id)) #モデルキーをTwitter内部IDに設定
            modeluser.twitter_name = api.me().screen_name #Twitter Id
            modeluser.access_key = access_key #アクセストークンキー
            modeluser.access_secret = access_secret #アクセスシークレットトークンキー
            modeluser.put() #データベースに反映

            self.response.out.write('登録完了')
        except Exception as e:
            self.response.out.write('エラ')

app = webapp2.WSGIApplication([ ('/', mainHandler), ('/callback', callbackHandler) ])
```

　データストアにはキーという属性があり、ユニークな値を持ったエントリ作るときは```get_or_insert('unique value')```を使ってキーを設定すると便利です。また、ユニークな値を設定したキーは```key().name()```で取出し、キーを指定しなかった場合は自動的にランダムなIDが振られ```key().id()```でIDを取り出します。

##RIOT API

　RIOT APIを使うにはまず[Developers Site](https://developer.riotgames.com/)にLoLのアカウントでログインします。そこには下記の通りにAPIのリクエスト回数制限が明記されています(2014/02現在)。

- 10リクエスト/10秒
- 500リクエスト/10分

　つまり、平均でリクエスト間隔が1.2秒を下回ってしまうとリクエスト制限を受ける危険性があるということです。LoLの1ゲームの最短時間はサレンダーできる20分として(もっと早い事もありますが)、遅延無くBOTを周回させれるのは理論上1000ユーザが最大ということです。十分ですね。

　次に、実際に```/api/lol/{region}/v1.3/summoner/by-name/{summonerNames}```を使用してサモナー情報を取得してみましょう。[Developers Site上のAPIページ](https://developer.riotgames.com/api/methods#!/506/1636)でサモナーネームを入力して動作を確認できます。APIから取得するデータはJSON形式で返されます。今回はdjangoのsimplejsonを使ってJSONをパースしますので、まずdjangoをライブラリで指定します。

```app.yaml
application: example_app
version: 7
runtime: python27
api_version: 1
threadsafe: true

libraries:
- name: django
  version: "1.4" #使い慣れたバージョンを指定

handlers:
- url: /registration
  script: main.app
  secure: always
```

　Pythonでユーザがサモナーネームを入力するとサモナーIDを返すウェブアプリを実装します。

```main.py
#! -*- coding: utf-8 -*-
from google.appengine.api.urlfetch import fetch
from django.utils.simplejson import loads

import webapp2
from urllib import quote
from time import mktime
from cgi import escape

from datetime import datetime

RIOT_KEY = '***********************************'

def getId(resion, summoner_name): #サモナーID取得関数
    #APIからサモナー情報を取得する
    result = fetch('https://prod.api.pvp.net/api/lol/'+resion+'/v1.3/summoner/by-name/'+quote(summoner_name)+'?api_key='+RIOT_KEY)
    if result.status_code == 200:
        #JSONパーサへ渡してIDと名前を返す
        j = loads(result.content)[summoner_name.lower().replace(' ', '')]
        return j['id'], j['name']
    else:
        return -1, None

class registrationHandler(webapp2.RequestHandler):
    def get(self):
        #取得開始ボタンを出力
        self.response.out.write('<form method="POST" action="./registration"><input type="radio" name="resion" value="1" checked="checked" />NA<input type="radio" name="resion" value="2" />EUW<input type="radio" name="resion" value="3" />EUNE<input type="text" name="summoner_name" /><button type="submit">取得</button></form>')
    def post(self):
        try:
            #入力された名前とリージョンを取得
            summoner_name = escape(self.request.get('summoner_name'))
            if escape(self.request.get('resion')) == '1':
                resion = 'na'
            elif escape(self.request.get('resion')) == '2':
                resion = 'euw'
            elif escape(self.request.get('resion')) == '3':
                resion = 'eune'

            #サモナーID取得関数を呼び出す
            summoner_id, summoner_name = getId(resion, summoner_name)

            #結果を出力する
            if summoner_id > 0:
                self.response.out.write('サモナーID：'+summoner_id+'サモナー名：'+summoner_name)
            else:
                self.response.out.write('サモナーが存在しないんですがそれは')
                return

        except Exception as e:
            self.response.out.write('エラ')

app = webapp2.WSGIApplication([ ('/registration', registrationHandler) ])
```

##次回予告

　長くなってきそうなので前編はここまでにします。後編ではQueue・Backend・Cron・Memchache辺りを使って実際にBOTを回す方法に触れられたらと思います。
→[後編](http://qiita.com/orfx/items/1a4278d16fb2a148dd99)書きました！