Twitterのuser_timelineから新着発言を取得して別サーバにPOST

Twitterに投稿した自分の新着発言を取得して、何らかの処理を行うことを考えます。

はてなブックマークWeb Hook」のように、発言が投稿された時点でTwitter側からこちらの指定したURLを呼び出してくれれば都合が良いですが、そんなものはないので、cronで定期的にポーリングすることを試みました。

取ってきた発言に対する処理本体は別サーバ (特にHerokuなどのRubyホスティングサービス) で実行することを想定して、別のHTTPサーバにJSON形式でPOSTするという仕様にします。
(cronでRubyスクリプトを動かせるサーバがあるなら、それを使えばいいじゃないかという話もありますが…)

ひとまず動くものができたので公開します。

テスト環境

ソースコード

user_timeline_hook.rb:

require 'rubygems'
require 'rubytter'
require 'dbm'
require 'json'
require 'net/http'
Net::HTTP.version_1_2

DB_FILENAME = "/var/local/timeline_status" # 状態記録用DBMファイル名
TL_COUNT = 10 # 一度に取得する最大発言数
SERVER_HOST = "localhost" # ポスト先サーバのホスト名
SERVER_PORT = 8080 # ポスト先サーバのポート番号
SERVER_PATH = "/post" # ポスト先サーバのパス

# コマンドライン引数からTwitterユーザ名を取得
if ARGV.size < 1
  abort("Usage: user_timeline_hook <user>")
end
user = ARGV[0]

# 状態記録用DBから、前回取得した最後の発言のステータスID取得
since_id = 0
DBM.open(DB_FILENAME) {|db|
  if db[user] != nil
    since_id = db[user].to_i
  end
}

# Twitter APIを呼び出してユーザタイムライン取得
max_status_id = since_id
client = Rubytter.new
statuses = []
opts = {:count => TL_COUNT}
opts[:since_id] = since_id if since_id > 0
statuses = []

client.user_timeline(USER_NAME, opts).each {|status|
  statuses << status
  if status.id > max_status_id
    max_status_id = status.id
  end
}

# 取得した最新のステータスIDを状態記録用DBに保存
DBM.open(DB_FILENAME) {|db|
  db[user] = max_status_id.to_s
}

# 取得した発言をJSON形式でPOST
Net::HTTP.start(SERVER_HOST, SERVER_PORT) {|http|
  header = {
    'Content-Type' => 'text/javascript+json; charset=utf-8'
  }
  body = statuses.to_json + "\r\n"
  response = http.post(SERVER_PATH, body, header)
}

実行:

ruby -Ku user_timeline_hook.rb <Twitterユーザ名>

のような形で実行します。

メモ

  • user_timelineの取得にはユーザ認証は必要ないので、Rubytter.newの引数は不要。これは楽ちん。
  • Twitterの発言に含まれるUTF-8文字列を正しくJSONエンコードするために -Ku が必要 (Ruby 1.8の場合)。
  • 最初にこのスクリプトを実行する前に、DBMファイル初期化処理が必要 (後述)。

おまけ: DBMファイル初期化

以下のスクリプトにより、DBMファイルを初期化します。実行するユーザによってはPermission Deniedになるので、DBMファイル (*.db) 自体は事前にtouchなどで作成しておく必要があります。

require 'dbm'

DB_FILENAME = "/var/local/timeline_status"

DBM.open(DB_FILENAME, 0644, DBM::NEWDB) {|db|
}

実行:

$ ruby create.db

おまけ: テスト用クライアント

user_timeline_hook.rbが期待通り動作することを確かめるため、以下のスクリプトをポスト先サーバとして起動します。ここではWEBrickを使っています。

test-server.rb:

#!/usr/local/bin/ruby -Ku
# -*- coding: utf-8 -*-
require 'rubygems'
require 'webrick'
require 'pp'
require 'json'

class RequestHandler < WEBrick::HTTPServlet::AbstractServlet
  def do_POST(req, res)
    puts "Request header:"
    pp req.header
    puts "Request body:"
    d = JSON.parse(req.body)
    pp d

    res.content_type = "text/plain"
    res.status = WEBrick::HTTPStatus::RC_OK
    res.body = "Succeeded\r\n"
  end
end

srv = WEBrick::HTTPServer.new({:DocumentRoot => '/',
                               :BindAddress => '127.0.0.1',
                               :Port => 8080})
srv.mount('/post', RequestHandler)
Signal.trap(:INT) { srv.shutdown }
srv.start

実行:

$ ruby -Ku test-server.rb 

このスクリプトを実行した状態で、user_timeline_hook.rb を実行することで、期待通りHTTPサーバへのPOSTが行われていることが確認できます。