作成日:2010/11/15
タグ:
Ruby,
LifeHacks,
Mail
メールでスケジュール管理
コマンドラインでスケジュール管理 の続きです。
lsch.rb をライブラリとしてメールインターフェースを被せました。
携帯電話などでコマンドメールを送ることにより、スケジュールの閲覧、登録、検索が出来ます。
コマンドメールの書式は以下。
スケジュールの閲覧
題名:[開始日[#終了日]]
本文:なし
スケジュールの登録
題名:[開始日[時][#終了日[時]]]
本文:
1行目 概要
2行目 場所
3行目以降 詳細
スケジュールの検索
題名:なし
本文:検索文字列
日時の書式
*[-]a a日後
a日間
a時間
a[a] a日,a時
a[a]bb a月b日
a時b分
a[a]/b[b] a月b日
a[a]-b[b] a月b日
a[a]:b[b] a時b分
a[a]bbcc a月b日c時
a[a]/b[b]/c[c] a年b月c日
a[a]-b[b]-c[c] a年b月c日
a[a]bbccdd a月b日c時d分
プログラムの本体部分 mailsch.rb のソースは以下。
#!/usr/bin/env ruby
#
#= メールでスケジュール管理
#
# コマンドメールを受け取り、
# スケジュールリストをメール送信もしくはスケジュールを登録する。
#
#ver.0.1.0:: 2008/09/20
#ver.0.1.1:: 2009/05/17 SMTPサーバを変更
#
#copyright:: Hiroyuki Mabuchi @ Comil[http://www.comil.jp/]
require 'tmail'
require 'kconv'
require 'net/smtp'
require 'lsch'
# ログ記録の設定
require 'logger'
log = Logger.new("#{ENV['HOME']}/mailsch.log", 'weekly')
log.level = Logger::DEBUG
# iCalファイルのパス
iCalFile = "/var/www/dav/mycalender.ics"
usage = <<EOS
[表示]
題名:[開始日[#終了日]]
本文:なし
[登録]
題名:[開始日[時][#終了日[時]]]
本文:
1行目 概要
2行目 場所
3行目以降 詳細
[検索]
題名:なし
本文:検索文字列
[日時の書式]
*[-]a a日後
a日間
a時間
a[a] a日,a時
a[a]bb a月b日
a時b分
a[a]/b[b] a月b日
a[a]-b[b] a月b日
a[a]:b[b] a時b分
a[a]bbcc a月b日c時
a[a]/b[b]/c[c] a年b月c日
a[a]-b[b]-c[c] a年b月c日
a[a]bbccdd a月b日c時d分
EOS
# 全日イベントかどうか
def full_day(event)
s = event.start
e = event.end
s != e && s.hour == 0 && s.min == 0 && e.hour == 0 && e.min == 0
end
# 複数日イベントかどうか
def multi_day(event)
full_day(event) && (event.start + 1 != event.end)
end
# 日付の出力形式
def date_str(d)
month = sprintf("%2d", d.month)
day = sprintf("%2d", d.day)
wday = Wdays[d.wday]
"#{month}/#{day}(#{wday})"
end
# 時刻の出力形式
def time_str(d)
hour = sprintf("%2d", d.hour)
min = sprintf("%02d", d.min)
"#{hour}:#{min}"
end
# イベントの表示
def list(icalpath, dates_line, to = nil)
res = ""
# iCalから出力
Lsch.new(icalpath).list_event(dates_line, to).each do |event|
start_date = date_str(event.start)
end_date = multi_day(event) ? date_str(event.end - 1) : ""
start_time = full_day(event) ? "" : time_str(event.start)
end_time = full_day(event) ? "" : time_str(event.end)
location = event.location
summary = event.summary
res << "#{start_date} #{start_time}-#{end_date} #{end_time},#{summary},#{location}\n"
end
res
end
# イベントの登録
def create(icalpath, dates_line, body)
res = ""
from, to = Lsch.parse_from_to(dates_line)
body_lines = body.split(/\n/)
summary = (body_lines.empty? ? "" : body_lines.shift)
location = (body_lines.empty? ? "" : body_lines.shift)
description = (body_lines.empty? ? "" : body_lines.join("\n"))
res << "Path: #{icalpath}\n"
res << "Start: #{from} - End:#{to}\n"
res << "Summary: #{summary}\n"
res << "Location: #{location}\n"
Lsch.new(icalpath).create_event(from, to, summary, location, description)
res << "Created !\n"
res
end
# イベントの検索
def search(icalpath, tarm)
res = ""
# iCalから出力
Lsch.new(icalpath).search_event(tarm.chomp).each do |event|
start_date = date_str(event.start)
end_date = multi_day(event) ? date_str(event.end - 1) : ""
start_time = full_day(event) ? "" : time_str(event.start)
end_time = full_day(event) ? "" : time_str(event.end)
location = event.location
summary = event.summary
description = event.description
res << "#{start_date} #{start_time}-#{end_date} #{end_time},#{summary},#{location}\n"
res << "#{description}\n"
end
res
end
# コマンドメールのメールアドレス
CmdAddress = "myaccount+sch@mydomain.com"
# コマンドメールを許す送信元アドレス
AllowAddress = %w[foo@docomo.ne.jp bar@mydomain.com baz@gmail.com]
# コマンドメール 標準入力から読み込み
cmd_mail_lines = readlines
log.debug "Mail Read."
# コマンドメールのオブジェクト
cmd_mail = TMail::Mail.parse(cmd_mail_lines.join)
# 送出メール
sch_mail = TMail::Mail.new
# コマンドメールを許す送信元アドレス以外のメールが来たら終了
exit unless AllowAddress.include?(cmd_mail.from.first)
cmd_subject = cmd_mail.subject
cmd_body = cmd_mail.body
open('/tmp/mailsch.log', 'a') do |f|
f.print "------------------------------\n"
f.print cmd_mail
res_body = ""
# cmd_mail.from.each {|from| res_body << "#From: #{from}\n"}
# cmd_mail.to.each {|to| res_body << "#To: #{to}\n"}
#
# res_body << "#Subject: #{cmd_subject}\n"
# res_body << "#Content-type: #{cmd_mail.content_type}\n"
# res_body << "#Sender: #{cmd_mail.sender}\n"
# res_body << "#Body: #{cmd_body}\n"
# 構文エラーに対して例外補足
log.debug "Parse Start."
begin
# タイトルおよび本文の有無から処理分岐
# タイトル:あり 本文:あり:: 登録
# タイトル:あり 本文:なし:: 表示
# タイトル:なし 本文:あり:: 検索
# タイトル:なし 本文:なし:: ヘルプ
if /^\s*$/ =~ cmd_subject
if /^\s*$/ =~ cmd_body
# タイトル:なし 本文:なし:: ヘルプ
res_body << usage
else
# タイトル:なし 本文:あり:: 検索
res_body << search(iCalFile, cmd_body)
end
else
if /^\s*$/ =~ cmd_body
# タイトル:あり 本文:なし:: 表示
res_body << list(iCalFile, cmd_subject)
else
# タイトル:あり 本文:あり:: 登録
# res_body << create(iCalFile, cmd_subject, cmd_body)
create(iCalFile, cmd_subject, cmd_body)
from, to = Lsch.parse_from_to(cmd_subject)
from = from.to_datetime_begin
to = from unless to
to = to.to_datetime_end
res_body << list(iCalFile, from, to)
end
end
rescue => evar
res_body << "構文エラー:#{evar}\n"
res_body << usage
end
log.debug "Parse End."
# 返信メールの作成
sch_mail.to = cmd_mail.from
sch_mail.from = CmdAddress
sch_mail.subject = "Schedule #{cmd_subject}"
sch_mail.set_content_type('text', 'plain', {'charset'=>'iso-2022-jp'})
sch_mail.body = Kconv.tojis(res_body)
sch_mail.write_back
f.print "++++++++++++++++++++++++++++++\n"
f.print sch_mail
log.debug "Mail Made."
end
# メール送信
Net::SMTP.start('localhost') do |smtp|
smtp.send_mail(sch_mail.encoded, sch_mail.from, sch_mail.to)
end
log.debug "Mail Sended."
適宜以下各変数の値を変更して下さい。
- iCalFile
- iCalファイルのパス
- CmdAddress
- コマンドメールのメールアドレス
- AllowAddress
- コマンドメールを許す送信元アドレス
AllowAddressに列記されたアドレス以外からのコマンドメールは無視し、また必ずコマンドメールの送信元に結果メールを送ります。
こうすることで、第3者からの操作を防ぎます。
メールサーバがコマンドメールを受け取った時に、この mailsch.rb を呼び出す仕組みを作ります。
うちのサーバは postfix を使っているので、postfix の拡張アドレス(アカウントの後に任意の語句を"+"で繋いでメールアドレスを作れる機能です。Gmailにも同様の機能がありますね。)を用いています。
メールサーバ上の自分のホームディレクトリに、
"|IFS=' ' && exec ~/bin/mailsch.rb || exit 75 #myaccount"
の内容のファイル .forward+sch を作り、先の mailsch.rb および
lsch.rb を ~/bin/ に置きます。
もちろん、パーミッションは適切に設定しておいて下さい。
特に、iCalファイルは HTTPサーバの実行ユーザー、mailschを使うユーザーどちらからも読み書き出来るパーミッションでないといけません。
かつ、anyone writable な設定は大変危険なので避けて下さい。
私は、iCalファイルを groupe writable にして、私のアカウントをHTTPサーバ実行ユーザーのグループに加えています。
こうすると、myaccount+sch@mydomain.com にコマンドメールを送ることで、スケジュール管理が出来るようになります。
blog comments powered by