[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

(DTPtechNote:1409) Re: [ruby] fwd_important-mail.rb(簡易メール転送クライアント)



#!/usr/bin/ruby
=begin
fwd_important-mail.rb	0.3
簡易メール転送クライアント

特徴;
rubyだけで動きます。
複数のメールアドレスからのメールを転送対象にできます(Fromフィルタ)。
Subjectの特定の文字列が含まれている場合も転送対象にできます(Subjectフィルタ)。
通常のメッセージ転送だけでなく、メール着信のおしらせだけをすることもできます。
添付ファイルを付けるかどうかを選べます。

動作環境;
ruby 1.8.2が動くこと。
メールサーバーのプロトコルがAPOPであること。

使い方;
メールサーバーの設定や必要なメールアドレスの設定を変数に入れてください
実行ユーザーのオーナーで、パーミッションを700にセットしてください。
(テスト)
ターミナル上から、このプログラムが置いてあるディレクトリに移動して
ruby ./fwd_important-mail.rb
とタイプします。
メール総数と、処理メールアドレスが随時出力された後、
It succeeded.
と出れば処理は正常終了。
(実際の運用)
適宜サーバー上のcrontabで定期的に実行してください。crontabの使い方については以下のサイトを参考にしてください。
http://www.linux.or.jp/JM/html/cron/man5/crontab.5.html
http://pop-club.hp.infoseek.co.jp/unix/crontab.html

注意;
メールボックスにあんまりMailを溜めると、動作が遅くなります。メールサーバー内の全てのメールのヘッダを見るので。
あたりまえですが、処理対象メールアドレスの中に転送先アドレスを含めると無限ループです。
メールサーバー上でなんらかのエラーになった場合、転送元アドレスにエラーとして送り返されます。転送元アドレスはエラーが来ても大丈夫なアドレスを選んでください。


histry;
2005.05.17	ver.0.1	midoreさんと共同作業
2005.05.18	ver.0.1.1	添付ファイルブロックの不正
2006.05.23	ver.0.2	Dateヘッダの訂正、ID-ligのパスを正しく、細かな訂正いろいろ
2006.05.25	ver.0.3	Subjectによるフィルタを追加
=end

#らいぶらりー
require "socket"
require 'net/pop'
require "net/smtp" # SMTPライブラリの読込
require "kconv" # kconvライブラリの読込


##======================================================変数と設定
popserver = '【ここにPOPサーバー名をいれる(例:pop.asahi-net.or.jp)】'##POPサーバー名(受信)
smtpserver = '【ここにSMTPサーバー名をいれる(例:mail.asahi-net.or.jp)】'##SMTPサーバー名(送信)
accunt = '【ここにアカウント名を入れる(例:hogehoge)】'#アカウント
passwd = '【ここにパスワードを入れる(例:fugafuga)】'#パスワード

#from行にこれらのアドレスが含まれていれば、転送処理対象とする。「false」と書けば、Fromフィルタは機能しない。
target_From = '【ここに処理対象のメールアドレスを入れる。複数のアドレスは「|」で区切ること。正規表現つかえます。こんな感じ。(例;(hoghoge@mac.com|ym3s-ickw@asahi-net.or.jp|@thinks-net.co.jp))】'
#Subject行にこれらのキーワードが含まれていれば、転送処理対象とする。「false」と書けば、Subjectフィルタは機能しない。
target_Subject = '【ここにキーワードを入れる。複数のキーワードは「|」で区切ること。正規表現つかえます。こんな感じ。(例;(\[ruby-list:\d+\]|hogehoge|fugafuga))】'
fromaddress = '【ここに転送元アドレスを入れる(例:ym3s-ickw@asahi-net.or.jp)】'#転送元アドレス
toaddress = '【ここに転送先アドレスを入れる(例;hogehoge@dk.pdx.ne.jp)】'#転送先アドレス

nobody = false#メール着信のおしらせだけならtrue。メッセージも転送するならfalse
attachments = false#添付ファイルを転送するかどうか? 転送するtrue, 転送しないfalse


#必要なファイル このプログラムと同じ階層に下記のファイルができます。
mail_idlog = 'm_id.txt'#既読メールのIDを保存しておくファイル
mail_idlog = File.join((File.dirname(File.expand_path(__FILE__))), mail_idlog)





##======================================================メソッド
#-------------------------------------------------------def error message
def my_error(errMess)
  puts "Error!\n   orz" + errMess
  exit#プログラム終了
end


#-------------------------------------------------------def my_exist
#########ファイル読み書き用メソッド
#ファイルの存在確認
def my_exist(filepath)
	unless FileTest.exist?(filepath) then#もしなかったらファイルをつくる
		File.open(filepath, "w") {|fh|
			fh.puts("")
		}
	end
	if !(FileTest.readable_real?(filepath)) then#読み込み不可なら
		my_error("Don't Read This File.")
	elsif !(FileTest.writable_real?(filepath)) then#書き込み不可なら
		my_error("Don't Write This File.")
	end
end

#-------------------------------------------------------def fileread
#ファイル(filepath)を読み込んで、ハッシュを返す。値の初期値はfalse
def fileread(filepath)
	myhash = Hash.new
	File.open(filepath, 'r') { |io|
		while line = io.gets
			line.chomp!#改行を削除して
			myhash[line] = false#初期値はfalse
		end
	}
	return myhash
end

#-------------------------------------------------------def
#filepathにstrを新規書き込み
def filewrite(filepath, str) 
	File.open(filepath, 'w') { |file|
	file.puts str
}
end
#-------------------------------------------------------def
#フラグの立っているキーを文字列化
#ハッシュ(myhash)を渡されたら、tureの値を持つキーだけを集めて(falseを値にもつキーを削除して)、改行でひとかたまりの文字列にする
def hash2str(myhash)
	myhash.delete_if {|key, value| value == false}
	str = myhash.keys.join("\n")
	return str
end



#########ここからメール送信用メソッド
#-------------------------------------------------------メールテキスト作成メソッド
def make_mail_text(subject, date, from, to, multipart_header, body)
# メールのテキストの作成
mail_text = <<-EndOfMail
Subject: Fwd:#{subject}
Date: #{date}
To: #{to}
From: #{from}
Content-Type: text/plain; charset='iso-2022-jp'
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
#{multipart_header}
#{Kconv::tojis(body)}
EndOfMail
return(mail_text)
end

#-------------------------------------------------------メールの送信
def send_mail(smtpserver, src, from, to)
	timeout(20) do
		Net::SMTP.start( smtpserver, 25 ) do |session|
			session.send_mail( src, from, to )
		end
	end
end

#-------------------------------------------------------メールの全体からボディだけを返す
#実はここは使っていない^^参考程度に
def my_body(str)
	startpoint = str.index( "\r\n\r\n" ) + 4
	return str[ startpoint, str.size - startpoint ].to_s 
end

#こっちでやってます
module Net
	class POPMail
		def body
			self.all[self.header.size .. -1]
		end
	end
end




##======================================================メイン
my_exist(mail_idlog)
myhash = fileread(mail_idlog)


begin
	filter_f = false#フィルタを行うかどうかのフラグ
	if target_From then
		address_reg = Regexp.new("^From: (.*#{target_From}[^\n]*)")#正規表現の生成
		filter_f = true
	else
		address_reg = false
	end
	if target_Subject then
		subject_reg = Regexp.new("^Subject: .*#{target_Subject}[^\n]*")#正規表現の生成
		filter_f = true
	else
		subject_reg = false
	end
	raise "No Conf." if filter_f == false
rescue 
	my_error($!)
end

	
	
	
begin

Net::APOP.start(popserver, 110, accunt, passwd) {|pop|
if pop.mails.empty? then#もしメールボックスが空だったら
	puts "No Mail"
else 
	mail_count=pop.mails.size #メール総数
	puts "Receved Mail  +#{mail_count}\n"
	pop.each { |mmail| ##1通1通のメールの処理
		mail_id=mmail.unique_id #ユニークidのゲット
		if myhash.key?(mail_id) then#既読メールであった
			myhash[mail_id] = true#値をセット
		else #myhashにふくまれていなければ
			mail_header=mmail.header#ヘッダをget
			
			if mail_header =~ address_reg or mail_header =~ subject_reg then#フィルタが設定されていれば
				mail_header =~ address_reg
				mailfrom = $1
				next if mailfrom =~ Regexp.new("#{toaddress}")#転送先と同じアドレスだったら次へ
				puts mailfrom#だれからのメールかをコンソールに出力
				mail_header =~ /^Subject: ([^\n]*)/
				mailsubject = $1#Subjectの取得
				mail_header =~ /^Date: ([^\n]*)/
				maildate = $1#Subjectの取得
				
				#メール着信のみのお知らせか、メッセージも転送するか
				if nobody then#お知らせだけなら
					mail_body = "You got a Mail!\nFrom: " + mailfrom + "\nSubject: " + mailsubject
				else#以下ボディ処理
					mail_body = mmail.body#ボディ全体の取得
					
					#添付ファイルの処理(大きなデータである可能性もあるので、メソッドにしない)
					#ヘッダ中にmultipartがあれば取得しておく
					if (mail_header =~ /^(Content-Type: multipart.+boundary=\"([^\"]+)\")/) then
						multipart_header = $1 + "\n"#Content-Typeヘッダの取得
						boundary = '--' + $2#区切り文字の取得
					else
						boundary = false
					end
					if !(attachments) then#添付ファイルを転送しない設定なら
						multipart_header = "\n"#Content-Typeヘッダをなしに
						if boundary then
							startpoint = mail_body.index("\r\n\r\n") + 4#最初の空行までのバイト数
							endpoint = mail_body.index(boundary, startpoint) -1#空行から区切り文字までのバイト数
							mail_body = mail_body[startpoint .. endpoint].to_s
						end
					end
				end#if nobody
					
				# smtpでメールの送信と例外処理
				begin
					mail_src = make_mail_text(mailsubject, maildate, fromaddress, toaddress, multipart_header, mail_body) # メールソースの作成
					send_mail(smtpserver, mail_src, fromaddress, toaddress)
				rescue
					my_error("send_mail\n$!")
				end
				myhash[mail_id] = true#キーと値をセットして既読とする
			end
		end  #if myhash.key?(mail_id)
	} #end pop.each
	#既読状態のものを書き込み
	str = hash2str(myhash)
	filewrite(mail_idlog, str)
end # pop.mails.empty?
} #end Net::APOP
rescue 
	my_error($!)
end
puts "It succeeded."