Lotus Notes 6.5のカレンダーをGoogle Calendarへ転送するRubyスクリプト

動機

Mozillaからスケジュール管理ソフトとして Sunbird Portable というのがあるのを知り、ToDo管理も含めて試そうと思い立ったものの、仕事ではLotus Notesのカレンダーなので、相互にやりとりできない。
SunbirdGoogle Calendarとの同期ができるので、あとLotus NotesGoogle Calendarとを同期させることができれば、Google Calendar経由でやり取りできる。(ちょっとまだるっこしいけれど)
で、Lotus NotesとGoole Calendarとの同期について検索していたところ、tokida氏ページを発見。
早速試してみたものの、Notesのバージョンの違い(当方はNotes 6.5)か上手くいかない。
そこからRubyの勉強しつつの試行錯誤が始まり、なんとか動かすことができた次第。

インストール

Rubyのインストールを含め次の手順で。RubyGemでインストールしたライブラリにはちょっと悩みました。

  1. Rubyのインストール
    1. http://www.garbagecollect.jp/ruby/mswin32/ja/ へアクセス
    2. Releaseからruby-1.8.7-p72-i386-mswin32.zip を取得
    3. 解凍したあと、binフォルダをPATHに入れる
  2. RubyGemのインストール
    1. http://rubyforge.org/frs/?group_id=126 へアクセス
    2. rubygems-1.3.1.zip を取得し、解凍
    3. 解凍したフォルダで ruby setup.rb を実行
  3. gcalapiのインストール
    1. http://rubyforge.org/frs/?group_id=2228&release_id=30319へアクセス
    2. gcalapi-0.1.2.gemを取得
    3. gem install gcalapi-0.1.2.gem としてインストール
  4. 後の方にあるバッチファイルを適当な名前で保存

gemの実行で zlib.dll がないと言われたときは、http://www.rubylife.jp/railsinstall/rubygems/index3.htmlを参考に準備。

設定

バッチファイルはRubyスクリプトになっているので、その中の定数定義を修正。

名称 設定内容
NOTES_SERVER Notesサーバ名
NOTES_DB メールDB名
NOTES_PASSWORD Notesパスワード。 nilにすると、実行時に入力となる
GCAL_ACCOUNT Google Calendarのアカウント(メールアドレス)
GCAL_PASSWORD Google Calendarのパスワード
GCAL_FEED Google CalendarのURL
PROXY_ADDR Google CalendarにアクセスするためのProxyサーバ名。nil だと直接アクセス
PROXY_PORT Proxy ポート
DURATION_TO_SYNC 転送範囲日数

FEEDのURLについて、XMLのリンクをコピーでよいですが、そのままでは上手くいかないようです。その場合、コピーしたURLのgoogle.com以下を、*google.com/private/full'というふうに /private/full とする上手くいきます。

実行

設定後のバッチファイルを実行する

転送範囲

予定、イベント、確認、記念日の4種類。
Notesの設計によっては上手くいかないものがあるかもしれませんが。

バッチファイル本体

@echo off
ruby -x "%~f0" %*
pause
goto :EOF

#! ruby -Ks

require 'rubygems'	# gcalapiをGemでインストールしたときに必要らしい
require 'win32ole'
require 'kconv'
require 'googlecalendar/calendar'

# 各種設定
NOTES_SERVER   = 'server_name'   # Notesサーバ名
NOTES_DB       = 'database.nsf'  # メールDB名
NOTES_PASSWORD = 'password'      # Notesパスワード
GCAL_ACCOUNT   = 'mailadderss@gmail.com'
GCAL_PASSWORD  = 'password'
GCAL_FEED      = 'http://www.google.com/calendar/feeds/xxxx%40group.calendar.google.com/private/full'

PROXY_ADDR     = nil # 'proxy_server'
PROXY_PORT     = nil #  8080

DURATION_TO_SYNC = 30                 # 今日から同期を取る範囲の日数

def get_range
  t = Time.now
  st = t.strftime("%Y/%m/%d 00:00:00")
  t = Time.mktime(t.year, t.month, t.day, 0, 0, 0)
  t += 60 * 60 * 24 * DURATION_TO_SYNC - 1
  et = t.strftime("%Y/%m/%d %H:%M:%S")
  return st, et
end

def setup_notes
  ns = WIN32OLE.new('Lotus.NotesSession')
  if NOTES_PASSWORD
    ns.Initialize(NOTES_PASSWORD)
  else
    ns.Initialize
  end
  db = ns.GetDatabase(NOTES_SERVER, NOTES_DB)
  return db.GetView('Calendar')
end

def show_doc(doc)
  items = doc.items
  puts "----------"
  items.each { |item|
    print item.name, "=>"
    p item.values
  }
end

if PROXY_ADDR
  GoogleCalendar::Service.proxy_addr = PROXY_ADDR
  GoogleCalendar::Service.proxy_port = PROXY_PORT ? PROXY_PORT : 80
end

gcal_server  = GoogleCalendar::Service.new(GCAL_ACCOUNT, GCAL_PASSWORD)
gcal_cal     = GoogleCalendar::Calendar.new(gcal_server, GCAL_FEED)

stime, etime = get_range()
print stime, ' - ', etime, "\n" if $DEBUG

begin
  view = setup_notes()
rescue
  puts "Notesサーバへ接続できません"
  exit
end


# 今日から一定範囲のカレンダイベントを消去
st = Time.parse(stime)
en = Time.parse(etime)
begin
  gcal_cal.events(
           :'start-min' => st,
           :'start-max' => en,
           :'max-results' => 1000).each { |event|
     event.destroy!
  }
rescue
  puts "Google Calendarへ接続できません"
  exit
end

keys = { }

doc = view.GetFirstDocument
n = 0
while doc
  atype   = doc.GetItemValue('AppointmentType')[0]
  st      = doc.GetItemValue('StartDateTime')
  et      = doc.GetItemValue('EndDateTime')
  loc     = doc.GetItemValue('Location')[0]
  subject = doc.GetItemValue('Subject')[0]
  
  #show_doc(doc)
  st.size.times { |i|
    printf("%s:%s-%s %s\t%s\n", atype, st[i], et[i], subject, loc) if $DEBUG
    if (stime <= st[i] && st[i] <= etime) || (stime <= et[i] && et[i] <= etime)
      key = st[i]+et[i]+subject
      if !keys.key?(key) 
        keys[key] = 1
        printf(">> %s:%s-%s %s\t%s\n", atype, st[i], et[i], subject, loc) if $DEBUG
        print st[i],"-",et[i], " ", subject, "\n"
        event = gcal_cal.create_event
        event.st = Time.parse(st[i])
        event.en = Time.parse(et[i])
        event.title = subject.toutf8
        event.allday = (atype == "1" || atype == "2") ? true : false
        event.desc = ''.toutf8
        event.where = loc.toutf8
        event.save!
        n += 1
      end
    end
  }
  doc = view.GetNextDocument(doc)
end

print '全部で ', n, "文書\n"

__END__
:EOF

使用にあたって

スクリプトはパスワードを直接埋め込んだりして使うようになっているので、管理には十分注意ください。
Notesを使用しているのはたぶん会社内だと思われますが、Googleへのアクセスには社内のルールなどがあると思うので、それに従うべきです。またカレンダーは個人情報であり、会議の情報などはいわゆるContidential情報になるので、使用者の責任において運用するようにしてください。
スクリプトの運用の結果については一切関知しません。

Rubyについて

今日分かったのですが、Rubyの場合、既存のクラスを追記型で修正できるらしく、notes_lib.rbそのままで修正できたかも知れません。
が、まあ動くのでそのままにしてあります。

謝辞

スクリプトはtokida氏の情報がなければ作ることができませんでしたし、ベースはtokida氏のプログラムです。修正版の公開を快諾いただいたtokida氏に感謝します。