RubyでNotesのメールを書き出す
職場のメール環境がNotesだったのですが、社外出向することになり、Notesのない環境で過去メールをどうやって参照するのか問題となりました。Notes標準の機能では、本文は書き出せるのですが、添付ファイルが書き出せません。
そこでRubyの登場です。NotesはOLEサーバになっており、WIN32OLEでぐりぐりいじることが可能なのです。
notes.rb
require 'win32ole' class Log def initialize(s) @@logger ||= Proc.new { |x| puts x } @@logger.call(s) end def Log.register_logger(p) @@logger = p end end class Notes class Session def initialize(server, dbname) @server, @dbname = server, dbname @session = WIN32OLE.new('Notes.NotesSession') or return nil @db = @session.GetDatabase(@server, @dbname) end # Notes::Documentクラスのイテレータ def each all = @db.AllDocuments doc = all.GetFirstDocument while doc yield Notes::Document.new(doc) doc = all.GetNextDocument(doc) end end include Enumerable # 添付ファイルをdirに書き出す def extract_files(dir) self.each do |doc| doc.each_attachment do |obj| name = obj.name Log.new "\t#{name}" # 対象外の拡張子をチェックする ext = File.extname(name).downcase next if %w(htm html csv pif scr com bat).include?(ext) # 書き出しメソッドを呼び出し doc.extract_file(name, "#{dir}/#{name}") end end end # 添付ファイルをdir/fromに書き出す def extract_files_by_sender(dir, body_hash = nil) self.each do |doc| k = doc.addr(doc.from) # 本文はハッシュに入れておく if body_hash body_hash[k] ||= "" body_hash[k] << doc.to_s end # 出力先の作成 extract_dir = "#{dir}/#{k}" Dir.mkdir(extract_dir) unless File.exist?(extract_dir) doc.each_attachment do |obj| name = obj.name Log.new "\t#{name}" ext = File.extname(name).downcase next if %w(htm html csv pif scr com bat).include?(ext) doc.extract_file(name, "#{extract_dir}/#{name}") end end end end class Document def initialize(doc) @doc = doc end def each_attachment a = @doc.Items or return a.each do |obj| yield Notes::Attachment.new(obj) if obj.Type == 1084 # ATTACHMENT end end def from; @doc.From[0]; end def sendto; @doc.SendTo[0]; end def copyto; @doc.CopyTo[0]; end def subject; @doc.Subject[0]; end def posted; @doc.GetItemValue('PostedDate')[0]; end def body; @doc.GetItemValue('Body'); end def addr(s) if m = s.match(/CN=(\w+) (\w+)\/O=(\w+)/) s = "(#{m[3]}) #{m[2]} #{m[1]}" elsif m = s.match(/(\w+) (\w+)\/(\w+)/) s = "(#{m[3]}) #{m[2]} #{m[1]}" elsif m = s.match(/<([\w.-]+@[\w.-]+)>/) s = m[1] elsif m = s.match(/([\w.-]+@[\w.-]+)/) s = m[1] elsif s == '' s = '(unknown)' end s end def to_s ret = ["From: #{from}"] ret << "To: #{sendto}" ret << "Cc: #{copyto}" ret << "Subject: #{subject}" ret << "Date: #{posted}" ret << "" ret << body ret << "\n" ret.join("\n").gsub("\r", "") end def extract_file(name, path) obj = @doc.GetAttachment(name) or return begin obj.ExtractFile(path) rescue => e Log.new "**** #{e.message} ****" end end end class Attachment def initialize(obj) @obj = obj end def extract_file(path) @obj.ExtractFile(path) end def name @obj.Values[0] end end end
短いコードなので詳しくは解説しませんが、送信者別にフォルダを作ってそれぞれ本文と添付ファイルを書き出すようになっています。Notes::Document#addrでNotes特有のアドレス表記を若干変換していますがこれはひょっとすると社内仕様かもしれません。
extract-cui.rb
require 'notes' require 'optparse' if $0 == __FILE__ opt_hash = Hash.new opt_hash['dir'] = '.' ARGV.options do |opt| opt.on('-d [dir]') { |v| opt_hash['dir'] = v } opt.on('-s') { |v| opt_hash['same_folder'] = v } opt.parse! end dir = File.expand_path(opt_hash['dir']) if !File.writable?(dir) || ARGV.size == 0 Log.new "Usage: ruby #{$0} [-s] [-d dir] file [file...]" exit end body_hash = Hash.new ARGV.each do |arg| if m = arg.match(/(.*)@(.*)/) nsf, svr = m[1, 2] else nsf, svr = [arg, ''] end if db = Notes::Session.new(svr, nsf) Log.new "添付ファイル抽出...(#{arg})" if opt_hash['same_folder'] db.extract_files(dir) else db.extract_files_by_sender(dir, body_hash) end end end Log.new "本文保存..." body_hash.each do |k, v| Log.new "\t#{k}" File.open("#{dir}/#{k}/mail.txt", "w") do |w| w.puts v end end Log.new "終了" end
コマンドライン版のサンプルです。引数はローカル保存した*.nsfファイルです。サーバのメールボックスを直接指定することもできるのですが、危険なのでお勧めしません。^^;VisualuRuby版もあるのですが、省略。
半年以上経つのに何をやっていたかすぐに思い出せましたが、これはRubyだからですねえ。私の場合、PerlやVBAだと先月書いたコードでも思い出せなくなります。