LaterMail いろいろと修正

Hit Me Laterにインスパイアされた、とってもシンプルなリマインダーツール『Later Mail』 | IDEA*IDEA で取り上げていただいたおかげで、たくさんの方が http://lm.5-55.jp/ を使ってくださっています。ありがとうございます。
で、いろいろな方がいろいろな環境からメールを送ってこられるので、エラーになるケースもちらほら見受けられます。現象が確認でき次第、修正していますので、エラーが返ってきた方は、info@5-55.jp へメールいただけると助かります。

以下はエラー報告いただいたケースで解決したものです。(たぶん解決したんじゃないかなというものも含みます)

ケース1:docomo の携帯

"Message-Id:"ヘッダが "Message-ID:"になっていたため、正規表現でマッチせず、エラーになっていました。
正規表現のマッチングに「大文字小文字を区別しない」オプション(/i)を付けたので、解決済みです。

ケース2:MacOSXのMail

"Date:"ヘッダの形式が "2008年8月28日 5:02:54:JST"という形式だったので、そのまま Time.parse に食わせていたため、エラーになっていました。
NKFUTF-8 に変換してから "年月日"を "/"に変換して、Time.parse に通すようにしたので、このケースは解決済みです。(たぶん。確認待ち)
追記:MacOSXのMailの場合、"Date:"ヘッダ形式云々より、"Message-Id:"ヘッダのあとに"Date:"ヘッダがくるため、ファイルを書こうとしたとき Dateが未定義になっていました。
"Message-Id:"ヘッダ受信時点で Dateが未定義だったら現在時刻を使用するように修正したため、解決済みです。("Date:"自体使わない方がいいかも)

ケース3:Au(ezweb) の携帯

"Date:"ヘッダ自体を送ってこなかったため、"Date:"ヘッダが付かないケースは現在時刻を採用するように修正しました。このケースは解決済みです。(たぶん。確認待ち)

ケース4:Softbank(vodafone.ne.jp) の携帯

Postfix が「No recipient addresses found in message header」というエラーを吐いているので、ヘッダから送り先の抽出ができないといっているんだと思うんですが、原因不明。
とりあえずとれる対策として、"From "、"Return-Path:"、"Sender:"にメールアドレスがあって、"To:"がなかった場合は、そのメールアドレスを"To:"とするようにしてみました。
誰かが vodafone.ne.jp から使ってくれるのの確認待ちです。

latermail.rb

公開してたった1日、2日でずいぶん修正してしまったので、一応いまのところの最新ソースを貼り付けておきます。
"Message-Id:"をファイル名にしようとしてずいぶん無理をしているような気がするのと、メールの遅延などを考慮して "Date:"を使ったほうがいいなと思っていたんですが、最近はそんなにひどい遅延にお目にかかったことがないから、いっそ使わないで受信時刻をそのまま使ったほうがすっきりするかもと思っているので、そのうち、また書き直すかもしれません。

require 'time'
require 'nkf'

class LaterMail
  # ファイル名(時刻+Message-Id)が決まるまで、@bufに溜めておく
  def initialize(mdir = '')
    @dir = mdir
    @buf = Array.new
  end

  # 0 - 24              -->    指定時間をプラス
  # sunday - saturday    -->    次の指定曜日までの日数をプラス(曜日以外は入ってこないが、入ってきてもプラスしないだけ)
  def add(dt, to)
    week = { 'sunday' => 0, 'monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6 }

    t = Time.parse(NKF.nkf('-Jw -xm0', dt).gsub(/[年月日]/,'/'))
    if to
      if to =~ /\d/
        t += to.to_i * 3600
      elsif week[to]
        t += ]/).each { |wd| return wd if wd.include?('@') }
    nil
  end
      
  def write
    ct = 0
    from = to  = boundary = dt = nil
    from_write = to_write = tf = false
    io = nil
    while line = gets
      if boundary && line.include?("--#{boundary}")
        if (ct += 1) == 2
          io.puts commercial(to)
        end
      end
      if (mail = get_mail(line">*1
        user = mail.split('@')[0]
      end
      case line
      when /^From /i, /^Return-Path:/i, /^Sender:/i
        from = mail
        @buf << line
      when /^X-Original-To:/i
        to = user
        @buf << "X-Original-To: #{from}"
      when /for\s*<.*@5-55\.jp>/i
        to = user
        @buf << line
      when /^Delivered-To:/i
        to = user
      when /^Date:/i
        dt = add(line, to)
        @buf << "Date: #{dt.to_s}"
      when /^From:/i
        from_write = true
        @buf << from_message
      when /^To:/i
        to_write = tf = true
        @buf << "To: #{from}"
      when /^Content-Type: multipart/i
        boundary = line.split('"')[1]
        @buf << line
      when /^Message-Id:/i
        @buf << from_message   if !from_write && from
        @buf << "To: #{from}"   if !to_write
        @buf << line
        dt = add(Time.now.to_s, to)    unless dt
        io = open("#{@dir}#{dt.strftime('%Y%m%d%H%M%S')}.#{user}", "w")
        @buf.each { |bf| io.puts bf }
      else
        if tf
          next  if line =~ /^\s/
          tf = false
        end
        if io
          io.puts line
        else
          @buf << line
        end
      end
    end
    unless boundary
      io.puts commercial(to)
    end
  end
end

if __FILE__ == $0
# cat mail > ruby latermail.rb

  lmail = LaterMail.new("/home/hml/Maildir/tmp/")
  lmail.write
end


Creative Commons License

*1:6 - t.wday + week[to]) % 7 + 1) * 3600 * 24 t = Time.mktime(t.year, t.month, t.day, 7) end end t end def from_message "From: LaterMail " end def ago(to) if !to || to == '0' "reply immediately" elsif to =~ /\d/ "#{to} hours before replying" else "today is #{to}" end end def commercial(to) "\n--------------------------------------------\n" + "#{ago(to)} by http://lm.5-55.jp\n" + "and please try http://2.5-55.jp (#{NKF.nkf('-jW -xm0', '通販Go!Go!')})\n\n" end def get_mail(line) line.split(/[\s<>]/).each { |wd| return wd if wd.include?('@') } nil end def write ct = 0 from = to = boundary = dt = nil from_write = to_write = tf = false io = nil while line = gets if boundary && line.include?("--#{boundary}") if (ct += 1) == 2 io.puts commercial(to) end end if (mail = get_mail(line