クリスマスまでに彼女を作る方法?

 12/23:18:00ぐらい

 独自RTをされると無限ループに陥る箇所があったので修正しました。

とても素晴らしいエントリーを見つけてしまったのでやる気を出してみた。既婚者だけど。

GoogleAppEngine + JRubyでクリスマスまでに彼女をつくる方法

ただしRubyとかよくわからんのでPythonで似たようなものを実装。


とりあえず次の手順で仮想彼女を作成する事が?できるはず?

1:このファイルをダウンロードして解凍した後に

2:Python-Twitterのファイルをlib以下にpython-twitterという名前で保存

3:setting以下のtwitter.yaml、messages.yaml、call_rsponses.yaml、basic.yamlを編集

4:Google App Engineに各ファイルを登録

messages.yamlが自動発言用のデータ、call_responses.yamlが自動応答用のデータファイルになるので、頑張って充実させればさせるほど素敵なボットになると思います。。。多分。

一応データ設定例のひとつとして、ミュージカル「RENT」の台詞に関する自動発言と自動応答を行うBOTを実装しています。→ RENT BOT


とりあえず動くようにしました!レベルなので、今後時間を見つけて直していきます。


以下、やっつけの説明。時間があるときにでもきちんと書き直します。多分。

1:とりあえずGoogle App EngineのアカウントやSDKを取得

アカウント:https://appengine.google.com/
SDKhttp://code.google.com/intl/ja/appengine/

Google App Engineに関する詳しい説明は、詳しいサイトがたくさんあるので適当にググってください。

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

Google App Engineのアプリケーション設定ファイルのapp.yaml、cron.yamlを次のように作成します。

なお、Google App EngineのCronはアプリケーションのURLを叩く形で実行されます。今回は「/response」がリプライへの反応、「/post」が発言用のURLです。

・app.yaml

application: bot
version: 1
runtime: python
api_version: 1

handlers:
- url: /response
  script: batch.py
  login: admin
- url: /post
  script: batch.py
  login: admin

cron.yaml

cron:
- description: replay job
  url: /replay
  schedule: every 5 minutes
- description: post job
  url: /post
  schedule: every 15 minutes

cron.yamlの設定で発言の周期を設定することができます。

3:python-twitterを取得してラッパーを作成

PythonTwitter APIを扱うためのライブラリPython-Twitterを下記URLから取得します。

http://code.google.com/p/python-twitter/

またpython-twitterはそのままではGoogle App Engine上で動作しないので簡単なラッパーを作成します。

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

### 外部モジュールの読み込み ########################
import sys
# python-twitterをimport
import twitter

# twitter側の処理
class PythonTwitter:
    def __init__(self, account, password):
        self.message_list = []
        ### botアカウントにapi接続 ##########################
        def my_twitter_api_init(self, username=None, password=None,input_encoding=None, request_headers=None):
            """overriding twitter.Api.__init__ method not to 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
        self.api = twitter.Api(account, password)
    ### Replay発言を取得して格納 ##############################
    def getReplay(self, dt, id):
        replay_list = []
        replies = self.api.GetReplies(dt, id)
        page = 1
        while 19 <= len(replies):
            add_replies = self.api.GetReplies(dt, id, page)
            replies = replies + add_replies
            page += 1

        self.message_list.append('------ getReplay');
        for replay in replies:
            replay_list.append({'text':replay.text,
            'id':replay.id,
            's_name':replay.user.screen_name,
            'dt':replay.created_at,
            'time':replay.created_at_in_seconds})
            self.message_list.append('message:  ' + replay.text);
        return replay_list
    ### POST ######################################
    def post(self, message):
        return self.api.PostUpdates(message)

4:アプリケーション処理を作成

Google App Engineで動作する処理ファイルを作成します。基本的にはtwitter-pythonをラッパー経由で呼び出してPOST、またはReplayを取得してそれに応じた発言をPOSTするという流れです。

なお「とりあえず動けば良いや」で書いているので、計算量とかはあんまり考えていないです。なのでもっと効率的なロジックはたくさんあると思います。。。というよりあります。ごめんなさいちゃんと勉強します。

それと、リプライの取得はGoogle App Engineのデータストアに最終更新時間を持たせる事で若干効率よくしています。API経由での取得制限対策のためで、あくまで若干ですが。

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

# Google App Engineライブラリの読み込み
import wsgiref.handlers              
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

# アプリケーションパスを読み込み
import sys
import os
sys.path.append('lib')
sys.path.append('lib/python-twitter')
sys.path.append('class')
sys.path.append('model')
### 基本モジュールを読み込み
import random
import yaml
import re
### twitter処理を読み込み
import pythonTwitter
### modelを読み込み
#import report
### classを読み込み

### 設定値
setting_dir = 'setting/'
### つぶやき処理 ########################################################
class post(webapp.RequestHandler):
    def __init__(self):
        self.setting_dir = setting_dir
    def get(self):
        file_data = open(self.setting_dir + 'twitter.yaml').read()
        data = yaml.load(file_data)
        account = data['account'] 
        password = data['password'] 

        # Python Twitter経由でTwitterに接続  
        obj = pythonTwitter.PythonTwitter(account, password)  
        file_data = open(self.setting_dir + 'messages.yaml').read()
        data = yaml.load(file_data)
        messages = data['messages'] 
         
        message_num = len(messages)
        key = random.randint(0, message_num - 1)
        obj.post(messages[key])

### リプライ処理 ########################################################
class response(webapp.RequestHandler):
    def __init__(self):
        self.setting_dir = setting_dir
    def get(self): 
        file_data = open(self.setting_dir + 'twitter.yaml').read()
        data = yaml.load(file_data)
        account = data['account'] 
        password = data['password'] 

        file_data = open(self.setting_dir + 'basic.yaml').read()
        data = yaml.load(file_data)
        default_response = data['default_response']  

        # call & response定義
        file_data = open(self.setting_dir + 'call_responses.yaml').read()
        data = yaml.load(file_data)
        call_responses = data['call_responses'] 
        # replay Modelの取得
        import replay
        rModel = replay.ReplayStatusModel()
        
        last = rModel.getLastStatus()
        # Python Twitter経由でTwitterに接続  
        obj = pythonTwitter.PythonTwitter(account, password)   
        replies = obj.getReplay(last['dt'], last['id'])
        if last['id'] == None:
            init_flag = True
        else:
            init_flag = False
        
        last_id = last['id']
        last_time = last['time']
        last_dt = last['dt']

        for replay_item in replies:
            if init_flag == False:
                replay_message = replay_item['text']
                to_pattern = re.compile('^.?@' + account + ' ')
                replay_message = to_pattern.sub('', replay_message)
                replay_message = replay_message.strip()
                response_result = False
                for list in call_responses:
                    if list['call'] == replay_message:
                        response_message = list['response']
                        response_result = True
                
                if response_result == False:
                    response_message = default_response

                obj.post('@' + replay_item['s_name']  + ' ' + response_message)

            if last_time < replay_item['time']:
                last_id = replay_item['id']
                last_time = replay_item['time']
                last_dt = replay_item['dt']

        rModel.setLastStatus({'id':last_id, 'time':last_time, 'dt':last_dt})

                                    
### メイン処理 ########################################################
def main():                          
    application = webapp.WSGIApplication([
    ('/post', post),
    ('/response', response)
    ], debug=True)
    wsgiref.handlers.CGIHandler().run(application)
                 

5:リプライ取得時間格納用のモデルを作成

リプライの最終取得時間の格納用のモデルを次のように作成します。ここではGoogle App Engineのデータストア用のモデル定義とデータ処理用のクラスをまとめて記述してあります。

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

from google.appengine.ext import db

### Replay状態関連のデータモデルクラス ####################################
class ReplayStatus(db.Model):
    update_id = db.IntegerProperty()        
    update_dt = db.StringProperty()        
    update_time = db.IntegerProperty()   
    update_status = db.StringProperty()   
    create_dt = db.DateTimeProperty(auto_now_add=True)
### ReplayStatusモデルに関する処理クラス ####################################
class ReplayStatusModel:                      
    def getLastStatus(self):
        if 0 < ReplayStatus.all().count():
            data_set =  ReplayStatus.all().order('-create_dt').fetch(1)
            return {'id':data_set[0].update_id,
            'time':data_set[0].update_time,
            'dt':data_set[0].update_dt}
        else:
            return {'id':None, 'time':None, 'dt':None}

    def setLastStatus(self, update_info):
        if 0 < ReplayStatus.all().count():
            update_obj = ReplayStatus.all().order('-create_dt').fetch(1)
            dt_obj = update_obj[0]
            dt_obj.update_id = update_info['id']
            dt_obj.update_time = update_info['time']
            dt_obj.update_dt = update_info['dt']
        else:
            dt_obj = ReplayStatus(update_id=update_info['id'],
            update_time=update_info['time'], update_dt=update_info['dt'])
        dt_obj.put()

6:ボット用のデータ、設定を作成

Twitterボットとして動作させるための設定を作成します。

twitter.yaml

ボット用のtwitterアカウント情報を記述します

account: <アカウント>
password: <パスワード>

・basic.yaml

ボット用の基本情報を記述します

bot_name: <ボットの名前>
bot_description: <ボットの説明>
default_response:<デフォルトの応答文>

・messages.yaml

自動POST用の文章を記述します。文章は複数記述できます。

messages:
- "メッセージその1"
- "メッセージその2"

・call_responses.yaml

自動応答用の想定問題集を記述します。callで定義される内容がリプライされた場合にresponseで定義された内容が返されます。応答セットは複数記述できます。

call_responses:
- call: "問いかけ 1"
  response: "応答 1"
- call: "問いかけ 2"
  response: "応答 2"

7:各ファイルをパッキングしてデプロイ

app_cfg.pyを利用してアプリケーションをでプロイします。