メールで Twitter、リファクタリング
メールで Twitter
暫く使ってるんだけどちょっと不満点もでてきた。Twitterの不安定でリクエストが通らないことがあり、それを見越して何度もメール再送するのつらい。
ので、少し改造しようかと思うから、まずリファクタリング。
もう少し仕様を考える
現状
http://apiwiki.twitter.com/Twitter-API-Documentation から抜書き、区切りとしてこれ位対応すればいいかな
- Timeline Methods
- statuses/friends_timeline
- 普通に目にするタイムラインと同等っぽいもの
- 公式RTの扱いについて home_timeline と混乱中
- statuses/home_timeline
- 普通に目にするタイムラインと同等のもの
- 公式RTの扱いについて friends_timeline と混乱中
- statuses/user_timeline
- id や呼び名を指定してその人の呟きをみるの
- statuses/mentions
- 人々の自分宛て(@自分)呟きをみるの
- statuses/friends_timeline
- Status Methods
- statuses/update
- 呟きの投稿
- statuses/update
その辺を踏まえて思うこと
リファクタリングの方針
上記まとめるとこんな感じになるかな
- 今 case when で分岐してる所は何かクラス作ってそのインスタンスへの send <メソッド名> で
- send に渡すメソッド名称、ユーザ入力(コマンドメールのメールアドレス)そのまま渡すのはセキュリティ的によろしくないので何か考える
- クラス定義部分をくくりだす一方実行部分を if $0==__FILE__ でくくったり
- 記録(ログ)ファイルのブロックが全体を覆っている辺りなんとかしたい
- 上記のように定義部分をくくりだせば実行部分は小さくなるのでまあまあそのままでもいいという感じになるかな
- Logger とかなんとか使う程でもないでしょう
- 上記のように定義部分をくくりだせば実行部分は小さくなるのでまあまあそのままでもいいという感じになるかな
- Net::HTTP とか Net::SMTP とか使ってローレベルから動かして行く方針は特に変えない
- タイムラインから取ってくる status の数の既定値は 200 にして置く
- 実際にはコマンドメールでの数値入力は苦ではないのであんまり意味無いかも
- 呼び出し方が限定的なので optparseとかオプション解析は特に行わない
- 何にもないときとか helpメッセージ表示くらいはしようか
そして
クラス名は Twitter と Statuses かな、Twitter::Statuses にしようか。それを sender で new して(使用者チェック)、recipient で指示されたメソッドを呼ぶ、引数に標準入力を与えたり与えなかったり。
#!/usr/bin/ruby # -*- coding: utf-8 -*- require 'nkf' require 'net/http' require 'net/smtp' Net::HTTP.version_1_2 class TwitterError < StandardError; end class Twitter User = Struct.new :acount, :password Users= { '<投稿者メールアドレス>' => User.new('<ツイッターアカウント名>', '<そのパスワード>') } Users['<二番目のメールアドレス>'] = Users['<投稿者メールアドレス>'] Address = ['twitter.com', 80] class Statuses Path = '/statuses/' Commands = Hash.new do |h, key| raise TwitterError, "invalid command '#{key}' called" end # Commands = Hash.new do |h, key| [ 'friends_timeline', 'timeline', 'user_timeline', 'mentions', 'update', ].inject(Commands){|cmds, cmd| cmds[cmd] = cmd.to_sym; cmds} def initialize(sender, recipient) @sender, @recipient = sender, recipient @user = Users[@sender] raise TwitterError, "sender '#{sender}' is not trusted user" unless @user end # def initialize(sender, recipient) def exec(lines) send Commands[@recipient.split('@').first.split('-').last], body(lines) end # def exec(lines) def update(lines) doing = lines[0].chomp request = Net::HTTP::Post.new "#{Path}update.json" request.basic_auth @user.acount, @user.password response = nil Net::HTTP.start(*Address) do |http| response = http.request request, "status=#{NKF.nkf('-Jw', doing)}" end # Net::HTTP.start('twitter.com',80) do |http| [doing, response.body].join "\n\n" end # def update(lines) def friends_timeline(lines) counting = lines[0].chomp count = /\A\d+/=~counting ? [counting.to_i, 200].min : 200 request = Net::HTTP::Get.new "#{Path}friends_timeline.rss" request.basic_auth @user.acount, @user.password response = nil Net::HTTP.start(*Address) do |http| response = http.request request, "count=#{count}" end # Net::HTTP.start(*Address) do |http| result = response.body.split("\n").select do |line| (line.include?('<description>')..line.include?('</description>')) ? true : false end.map do |line| line.sub(/^\s*<description>/, '').sub(/<\/description>\s*$/, '') end.map{|line| line.gsub(/&#(\d+?);/){ [$1.to_i].pack('U') }} Net::SMTP.start('localhost',25) do |smtp| smtp.send_mail <<-EOM, @recipient, @sender From: #{@recipient} To: #{@sender} Subject: #{@recipient.split('@').first} Date: #{Time.now} Message-Id: <#{Time.now.to_i}.#{@recipient}> #{NKF.nkf('-Wj', result.join("\n"))} EOM end # Net::SMTP.start('localhost',25) do |smtp| end # def friends_timeline(lines) alias :timeline :friends_timeline def user_timeline (lines) end # def user_timeline (lines) def mentions(lines) end # def mentions(lines) private def body(lines) loop{ break lines if 1 == lines.shift.length} end # def body(lines) end # class Statuses end # class Twitter if $0 == __FILE__ then sender, recipient, = ARGV File.open('/home/<ユーザ名>/projects/'+Time.now.strftime('%Y%m%d%H%M%S'), 'w'){ |f| f.puts sender, recipient, '' begin # rescue TwitterError => evar f.puts Twitter::Statuses.new(sender, recipient).exec($stdin.readlines) rescue TwitterError => evar then f.puts evar, '', $stdin.read end # rescue TwitterError => evar } # File.open('/home/<ユーザ名>/projects/'+Time.now.strftime('%Y%m%d%H%M%S'), 'w') end # if $0 == __FILE__
ちょっと
Commands = Hash.new do |h, key| raise TwitterError, "invalid command '#{key}' called" end # Commands = Hash.new do |h, key|
受け入れるコマンドのリスト、ハッシュの既定値で見付らないエラーを挙げる
[ 'friends_timeline', 'timeline', 'user_timeline', 'mentions', 'update', ].inject(Commands){|cmds, cmd| cmds[cmd] = cmd.to_sym; cmds}
コマンドのリストの登録、これはなんかちょっと変な気がする。ハッシュを使うのがそもそもおかしいのかな、どうしたら良いんだろう。
@user = Users[@sender] raise TwitterError, "sender '#{sender}' is not trusted user" unless @user
Userが見付らなかった時エラーを挙げる。上記 Commandsとエラーの挙げ方が違うのは、書いた時期が違うからでしょう。合わせた方が良いでしょう。
あとは元のスクリプトをコピペした感じ。如何でしょう。