Twicco Cloneをつくってみたよ

id:tottotiTwiccoみたいなのを作りたいんだけどなんとかならん?という相談を受けたのでPythonを使ったOAuthの勉強がてらにいろいろいじってみましたよ

OAuth関係の語句とかはかなり適当に覚えてるっぽいので間違ってたら教えてください。ソレ以上にPython的におかしい箇所が多そうなので見つけたら教えていただければ(´;ω;`)Pythonリハビリチュウデスノ

想定する動作と環境

想定する動作は「自分(BOTTwitterアカウント等)に対してPOSTされたmentionを自分の発言としてPOSTする」って感じです。

想定する環境はPythonスクリプトとして実行できる感じデス…ごめんなさいLinuxしか想定してないです(´・ω・`)

ざっくりとした流れ

Twicco Clone的なものを動かすための手順は次のような感じですねー

  • CONSUMER情報の準備
  • 各種モジュールの用意
  • 情報保存用のディレクトリの用意
  • 設定ファイルの記述
  • ユーザ認証情報(アクセストークン)の取得
  • twicco風スクリプトの実行

CONSUMER情報の準備

TwitterOAuth認証用のCONSUMER_KEYとCONSUMER_SECRETを取得します。詳しい手順はここらへんを参考にしました。

各種モジュールの用意

以下のモジュールを用意します

情報保存用のディレクトリの用意

以下のディレクトリを実行ロケーションに用意します

  • conf: CONSUMER情報の保存ディレクト
  • users: ユーザ情報を格納(複数ユーザOK)
  • tmp: 取得情報(どのmentionまで処理したか)を格納

設定ファイルの記述

CONSUMER情報をconf/twitter.confに以下の形式で記述しますよ

# Twitter OAuth Parameter
CONSUMER_KEY:<CONSUMER KEY>
CONSUMER_SECRET:<CONSUMER SECRET>

ユーザアクセストークンの取得

取得用スクリプトの作成

こちらを参考に下記のような取得スクリプトを作成します

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

# Code from reppets.log 
# http://d.hatena.ne.jp/reppets/20100522/1274553529

from oauth2 import Client, Token, Consumer 
import os

# Read OAuth Parameter 
work_dir = os.path.dirname(os.path.abspath(__file__)) + '/'
params = open(work_dir + 'conf/twitter.conf', 'r')
conf = {}
for line in params:
  param_list = line[:-1].split(':')
  if 1 < len(param_list):
    conf[param_list[0]] = param_list[1]
params.close()
consumer_key = conf['CONSUMER_KEY'] 
consumer_secret = conf['CONSUMER_SECRET']

# define param splitter
def split_parameter(params):
  result_list = [tuple(param.split('=')) for param in params.split('&')]
  return dict(result_list)

# getting twitter user name
user_name = raw_input('Please input twitter user name:')

# Authentication OAuth
consumer = Consumer(consumer_key, consumer_secret)
client = Client(consumer, None)
result = client.request('http://api.twitter.com/oauth/request_token','GET')
request_token_map = split_parameter(result[1])
request_token = Token(request_token_map['oauth_token'], 
  request_token_map['oauth_token_secret'])

### URL display 
print 'Please access "http://api.twitter.com/oauth/authorize?oauth_token='+request_token.key+'".'
pin = raw_input('Please input PIN:')
request_token.set_verifier(pin)

# POST clinent Token
client.token = request_token
result = client.request('http://api.twitter.com/oauth/access_token','POST')

access_token_map = split_parameter(result[1])
print result[1]
print 'User key: '+access_token_map['oauth_token']
print 'User secret: '+access_token_map['oauth_token_secret']

user_key = """USER_KEY:""" + access_token_map['oauth_token'] +"""
USER_SECRET:""" + access_token_map['oauth_token_secret'] + """
"""

# create user.key file
f = open(work_dir + 'users/' + user_name, 'w')
f.write(user_key)
f.close()
raw_input('Push any key to quit.')
スクリプトの実行

以下の手順でアクセストークンを取得します

  • シェル上で上記のPythonスクリプトを実行
  • 対象となるアカウントを入力
  • 表示されたURLにブラウザからアクセス
  • 該当アカウントで認証後に暗証番号を取得し、コンソール上に入力
$python twitterOAuthInit.py

Please input twitter user name:<ボット用のアカウントを入力>
#このURLにブラウザからアクセス
Please access "http://api.twitter.com/oauth/authorize?oauth_token=<oauthトークン>".
Please input PIN:<暗証番号を入力>

上記手順を実行するとusersディレクトリにアクセストークン情報が記録された<アカウント名>.keyファイルが作成されます。

twicco風スクリプトの作成

アクセストークン情報が取得できたので実際のTwitter関連の処理を作成しますかねー

スクリプトの作成

下記のようなスクリプトを作成してPythonスクリプトとして実行します。スクリプトの内容はmentionを取得して内容を発言するだけ、という単純動作です。

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

from urllib import urlencode
from oauth2 import Client, Consumer, Token
import simplejson as json
import re
import os

# Define Twicco Clone
class TwiccoClone:
  ####################################
  # Constructor of TwiccoClone
  def __init__(self):
    # Get Dir Info
    self.work_dir = os.path.dirname(os.path.abspath(__file__)) + '/'
    # Get Twitter Param
    self.conf = self._param()
    # Define API URL
    self.api_url = 'http://api.twitter.com/1/'
  ####################################
  # Get Parameter
  def _param(self):
    conf = {}
    # Read Twitter config
    params = open(self.work_dir + 'conf/twitter.conf', 'r')
    for line in params:
      param_list = line[:-1].split(':')
      if 1 < len(param_list):
        conf[param_list[0]] = param_list[1] 
    params.close()
    # Read user key config
    users = os.listdir(self.work_dir + 'users/')
    conf['users'] = []
    for user in users:
      params = open(self.work_dir + 'users/' + user, 'r')
      user_keys = {}
      user_keys['name'] = user
      for line in params:
        param_list = line[:-1].split(':')
        if 1 < len(param_list):
          user_keys[param_list[0]] = param_list[1] 
      params.close()
      conf['users'].append(user_keys)
    return conf
  ####################################
  # Get Mensions
  def _mentions(self):
    last_mention_file = 'tmp/last_mention_' + self.conf['USER_NAME'] 
    # create empty file
    if os.path.isfile(self.work_dir + last_mention_file) == False:
      last_file = open(self.work_dir + last_mention_file,'w')
      last_file.write('')
      last_file.close()
    # Get last mentions
    last_file = open(self.work_dir + last_mention_file,'r')
    since_id = last_file.read()
    last_file.close()
    # last mentions exists
    if since_id != '':
      result = self.client.request(self.api_url + 'statuses/mentions.json?count=200&since_id=' + str(since_id))
    # first time
    else:
      result = self.client.request(self.api_url + 'statuses/mentions.json?count=200')
    # Get Mentions
    mentions = json.loads(result[1])
    # Execute Format Mentions
    r = re.compile('@' + self.conf['USER_NAME'] + ' ')
    count = 0
    for mention in mentions:
      user = mention['user']
      # keep back self loop
      if user['screen_name'] != self.conf['USER_NAME']:
        # init time not post
        if since_id != '':
          self._post({'user_name':user['screen_name'],
          # cut out target signature
            'text':r.sub('', mention['text'])})
        # save last mention
        if 0 == count:
          last_file = open(self.work_dir + last_mention_file,'w')
          last_file.write(str(mention['id']))
          last_file.close()
        count += 1
  ####################################
  # Post Message
  def _post(self, tweet=None):
    if tweet != None:
      # format message
      message = tweet['text'] + ' [ from @'+ tweet['user_name']  +' ]'
      # post message
      self.client.request(self.api_url + 'statuses/update.xml','POST',
        urlencode({'status':message.encode('utf_8')}))
  ####################################
  # Execute Process
  def echo(self):
    users = self.conf['users']
    for user in users:
      self.conf['USER_NAME'] = user['name']
      # Get Twitter API Session
      self.client = Client(Consumer(self.conf['CONSUMER_KEY'], self.conf['CONSUMER_SECRET']), Token(user['USER_KEY'], user['USER_SECRET']))
      self._mentions()

####################################################
# define main function
def main():
  import sys
  import codecs
  sys.stdout = codecs.getwriter('utf_8')(sys.stdout)
  ### Execute Code
  twicco = TwiccoClone()
  twicco.echo()
# when exexute this script
if __name__ == "__main__":
  main()
スクリプトの実行

実行時はこんな感じでOKです

$python twiccoClone.py

一応モジュールとしてインポート出来るように書いたので、適当にインポートして使っても大丈夫なはずです...たぶん(´・ω・`)それと、定期的に実行する場合はCRONを使ってくださいなー

以上ですよー

こんな感じにやればTwiccoっぽい処理が作れますよーというサンプルでした。

動くかどうかのお試し実装のためエラー処理とかは省いています(´;ω;`)ジカンガナカッタ ので実際に使う場合はそのあたりを補完したほうがいいです。

上記スクリプトファイルは以下においておきますね
http://plasticscafe.sakura.ne.jp/twicco_clone.tgz