メールで Twitter、リファクタリング続き

メールで Twitter

  1. Twitterへのメールからの投稿 - Rubyとか Illustratorとか SFとか折紙とか
  2. メールによる Twitterの参照 - Rubyとか Illustratorとか SFとか折紙とか
  3. メールで Twitter、リファクタリング - Rubyとか Illustratorとか SFとか折紙とか

機能追加の前のリファクタリングを続ける。

テスト或はビヘイビア(BDD)

テストやBDD(振る舞い駆動開発)なんだけど、実際にコマンドメールを送ってみて思ったように動くかどうかでやってます。テスト用のAPIサーバ作るとか考えるとちょっとやってらんないので実地動作でテストです。
ユニットテストとか考えるほどユニット化されていないというのもあります。
今回のリファクタリングでメソッドが増えるのでそろそろ考え時かも。

リクエストとレスポンスの処理

ソースを見てると Twitter::Statuses#updateメソッドと Twitter::Statuses#friends_timelineメソッドではツイッターAPIにリクエストを送る所が何か似てる。またその後の処理についても、レスポンスからメール作って返信するというのは追加したいほかのコマンドメールでも共通して使いそうだ。
なのでその辺を個別のメソッドに括りだす。

あと、記録(ログ)ファイルの辺りも。

というわけでソース

#!/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
      response = request 'update', :post, :status => doing
      [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
      reply request('friends_timeline', :get, :count => count.to_s)
    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)

    def request(to, post, args)
      req = if 'post' == post.to_s.downcase then
        Net::HTTP::Post
      else # req = if 'post' == post.to_s.downcase then
        Net::HTTP::Get
      end.new "#{Path}#{to}.rss"
      req.basic_auth @user.acount, @user.password
      arg_str = args.inject([]) do |res, k_v|
        res << "#{k_v[0]}=#{NKF.nkf '-Jw', k_v[1]}"
        res
      end.join('&amp;')
      response = nil 
      Net::HTTP.start(*Address) do |http|
        response = http.request req, arg_str
      end # Net::HTTP.start('twitter.com',80) do |http|
      return response
    end # def request(to, args)
    
    def reply(response)
      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 reply(response)
  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.')+recipient, 'w') do |l| 
    l.puts sender, recipient, ''
    begin # rescue TwitterError => evar
      l.puts Twitter::Statuses.new(sender, recipient).exec($stdin.readlines)
    rescue TwitterError => evar then
      l.puts evar, '', $stdin.read
    end # rescue TwitterError => evar
  end # File.open('/home/<ユーザ名>/projects/'+Time.now.strftime('%Y%m%d%H%M%S.')+recipient, 'w') do |l| 
end # if $0 == __FILE__