Hatena::ブログ(Diary)

SPEAKER BREAKA このページをアンテナに追加 RSSフィード

2009-08-16

GAE/Python で Twitter bot を作る(後編)

準備編 の続きは明日書くとか言っときながら、間が空いてしまいましたが後編を。

残るは肝心のハンドラ部分を実装していきます。


views.py の作成

main.py で指定したように、

  • /:TOP画面。ハンドラは MainHandler
  • /ssbbot/mumble/update/:cron がここを叩いて更新処理を走らせる。ハンドラは BotHandler

という感じで views.py を実装して行きましょう。


#!/usr/bin/env python
# -*- coding:utf-8 -*-
import logging

from google.appengine.ext import webapp

import twitter
import ssb
import bitly

from models import MumbleModel

def my_twitter_api_init(self,
                        username=None,
                        password=None,
                        input_encoding=None,
                        request_headers=None):
    """Monkey patche for twitter.Api.__init__ method.
    Just change _cache do not use FS cache.
    """
    import urllib2
    from twitter import Api
    self._cache = None
    
    self._urllib = urllib2
    self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
    self._InitializeRequestHeaders(request_headers)
    self._InitializeUserAgent()
    self._InitializeDefaultParameters()
    self._input_encoding = input_encoding
    self.SetCredentials(username, password)
twitter.Api.__init__ = my_twitter_api_init


class MainHandler(webapp.RequestHandler):
    def get(self):
        """
        """
        self.response.out.write("Stack Stock Books つぶやき Bot")

class BotHandler(webapp.RequestHandler):
    """Bot 更新用ハンドラ"""
    def get(self):
        """
        """
        # 最新の SSB つぶやき取得
        ssbapi = ssb.Api()
        mumble = ssbapi.get_mumbles(include_books=False)[0]
        logging.debug("mumble: %s", mumble)

        key_name = MumbleModel.key_format % dict(mumble_id=mumble.mumble_id)
        mm = MumbleModel.get_by_key_name(key_name)

        if mm is not None and mm.time >= mumble.time:
            # 更新情報なし
            logging.info("No update: mumble_id: %s", mumble.mumble_id)
            self.response.out.write("No update")
            return

        # 書籍情報取得
        book = ssbapi.get_book(mumble.book_id)
        mumble.book = book
        
        # 投稿文言生成
        twitterapi = twitter.Api(username="TWITTER_USER", password="TWITTER_PWD")
        user = ssbapi.get_user(mumble.user_id)
        status = self._mumble_string(user, mumble)
        if len(status) > 140:
            status = status[:139] + u'\u2026'

        # Twitter 更新
        result = twitterapi.PostUpdate(status)
        logging.info("status: %s\nresult: %s", status, result)

        # 更新情報を登録
        mm = MumbleModel(key_name=key_name,
                         mumble_id=mumble.mumble_id,
                         time=mumble.time)
        mm.put()
        self.response.out.write("OK")

    def _mumble_string(self, user, mumble):
        url = mumble.uri
        api = bitly.Api()
        api.set_account("BITLY_LOGIN", "BITLY_APIKEY")
        try:
            url = api.shorten(url)
        except bitly.BitlyResponseError:
            pass
        
        return u"%(url)s %(nick)s『%(title)s』%(body)s" \
               % dict(nick=user.nick,
                      title=mumble.book.title,
                      body=mumble.body,
                      url=url)

BotHandler では、

  1. Stack Stock Books から最新のつぶやき取得
  2. Datastore に保存した前回のつぶやき情報と比較。更新が無ければ終了。
  3. 新しい情報ならば、Twitter への投稿文言生成。URL は bit.ly API を利用して短縮。
  4. 文言が140文字を越えていたら、139文字+'…' として切り詰め処理。
  5. Twitter へ投稿
  6. Datastore に最新つぶやき情報を登録

という処理をしています。


完成!

さて、これで簡単な Twitter bot ができました。ファイル構成は以下のようになっていると思います。

./ssbbot/
|-- app.yaml
|-- bitly.py
|-- cron.yaml
|-- main.py
|-- models.py
|-- simplejson
|-- ssb.py
|-- twitter.py
`-- views.py

あとはこれを GAEデプロイすれば完成です。

2009-08-10

GAE/Python で Twitter bot を作る(準備編)

先のエントリで bit.ly API モジュールやら Stack Stock Books API モジュールを作ったので、Google App Engine 上で動作する Twitter bot を作ってみます。


先のエントリとやらはこの辺です。


どんな処理にしましょうか?

ざっくりとこんな感じで。

  1. Stack Stock Books から最新のつぶやき取得
  2. Datastore に保存した前回のつぶやき情報と比較
  3. 新しい情報ならば、Twitter への投稿文言生成
  4. Twitter へ投稿
  5. Datastore に最新つぶやき情報を登録
  6. これらの処理を一定間隔で回す

ということで、URL 的にはこんな感じに。

  • /:紹介文でも表示しましょう
  • /ssbbot/mumble/update/:cron がここを叩いて上記処理を走らせる

必要なモジュールのソースをダウンロード

以下のモジュールのソースを取得します。

そして、先のエントリで自作したモジュール


これらのモジュールソースコードを ssbbot ディレクトリ以下に配置します。

./ssbbot/
|-- bitly.py
|-- simplejson
|-- ssb.py
`-- twitter.py

ちなみにキモの python-twitter は 0.6 を使用していますが、Api クラスにおいてファイルキャッシュを使っているために、素のままでは GAE で使えません。今回は以下のような Monkey patch を twitter.Api を利用する前に書いて、その場しのぎを…

def my_twitter_api_init(self,
                        username=None,
                        password=None,
                        input_encoding=None,
                        request_headers=None):
    """Monkey patche for twitter.Api.__init__ method.
    Just change _cache do not use FS cache.
    """
    import urllib2
    from twitter import Api
    self._cache = None
    
    self._urllib = urllib2
    self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT
    self._InitializeRequestHeaders(request_headers)
    self._InitializeUserAgent()
    self._InitializeDefaultParameters()
    self._input_encoding = input_encoding
    self.SetCredentials(username, password)
twitter.Api.__init__ = my_twitter_api_init

アプリケーション設定ファイルの作成

ssbbot ディレクトリに、アプリケーション設定ファイル app.yaml を以下のような内容で追加します。

application: YOUR_APP_ID
version: 1
runtime: python
api_version: 1

handlers:
- url: /ssbbot/.*
  script: main.py
  login: admin

- url: /.*
  script: main.py

今回は /ssbbot/以下の URL は cron (と管理者)だけが叩けるようにしたいので、login: admin で、管理者権限を持つユーザーのみがアクセスできるようにしています。


スケジュールタスク設定ファイルの作成

更新処理を cron で処理を走らせたいので、ssbbot ディレクトリにスケジュールタスク設定ファイル cron.yaml を以下のように追加します。

cron:
- description: update mumble job
  url: /ssbbot/mumble/update/
  schedule: every 30 mins

ここでの間隔は30分にしていますが、あまり間隔が空いてしまうと更新データの取りこぼしが出てしまいますし(今回のアプリでは最新の一件しか投稿しませんw)、かと言って短か過ぎると、利用 APIGAE の制限に引っかかったり迷惑をかけてしまうので程々にします。


models.py の作成

Twitter に投稿されたつぶやき情報を保持するため、MumbleModel という Datastore モデルを作成します。


ssb モジュールの Mumble クラスは多くのプロパティを持ちますが、今回は既に投稿済みかどうかを比較するためだけに使用するので、保持するプロパティは mumble_id と time、そしてデータストアに登録した日時だけにしています。


#!/usr/bin/env python
# -*- coding:utf-8 -*-
from google.appengine.ext import db

class MumbleModel(db.Model):
    """
    >>> mumble = Mumble()
    >>> mumble.mumble_id = 123
    >>> mumble.time = datetime(2009, 8, 3, 22, 34, 36)
    >>> mumble.put()
    >>> db.put(mumble)
    """
    key_format = "mumblemodel/%(mumble_id)s"
    
    mumble_id = db.IntegerProperty(required=True)
    time = db.DateTimeProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)

main.py の作成

ハンドラに行く前に、メイン処理を書いちゃいます。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

from views import *

def main():
    app = webapp.WSGIApplication([
        (r'/', MainHandler),
        (r'/ssbbot/mumble/update/', BotHandler),
        ], debug=True)
    run_wsgi_app(app)

if __name__ == '__main__':
    main()

よし、これで残りは views.py に肝心のハンドラを書くだけです。


明日あたりに続くっ!