plugins/mailcheckbot.nb
# -*-ruby-*-
#
# Copyright (C) 2004 Kazuhiro NISHIYAMA
# All rights reserved.
# This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the Ruby's licence.
#
=begin
BotConfig = [
{
:name => :MailCheckBot,
:Maildir => "~/Maildir/.ml.debian-security-announce",
:template => "biff(DSA): %{subject}s",
:channels => ['#nadoka'],
},
{
:name => :MailCheckBot,
:mh_dir => [
"~/Mail/ml/nadoka",
"~/Mail/ml/yarv-dev",
],
:channels => %w[#nadoka #nadoka_check],
:template => "biff(%{x-ml-name}s): %{subject}s - http://www.atdot.net/mla/%{x-ml-name}s/%{x-mail-count}d",
}
]
=end
require 'nkf'
class MailCheckBot < Nadoka::NDK_Bot
class Error < StandardError; end
class MailCheckInfo
def initialize
@biffed = Hash.new
update_entries do |fields|
true
end
end
def entries
files = []
@glob_patterns.each do |pattern|
files.concat(Dir.glob(pattern))
end
files
end
def mailread(filename)
fields = Hash.new("".freeze)
header_lines = ""
message_id = nil
File.foreach(filename) do |line|
case line
when /^\s*$/
break
when /^message-id:\s*/i
message_id = $'
return message_id, fields if @biffed.key?(message_id)
end
header_lines.concat(line)
end
header_lines.split(/^(?!\s)/).each do |line|
line = NKF.nkf("-e", line)
line.gsub!(/\s+/, ' ')
key, value = line.split(/:/, 2)
key.downcase!
value = value.to_s
value.strip!
# use last field if same keys found
fields[key.freeze] = value.freeze
end
return message_id, fields
rescue
# ignore missing files after glob
end
def update_entries
old_entries = @biffed.keys
new_entries = entries
(new_entries - old_entries).each do |filename|
mid, fields = mailread(filename)
next unless mid # the file is not probably a mail message
next if @biffed.key?(mid) # already biffed
yield(fields)
@biffed[mid.freeze] = true
end
if not(old_entries.empty?) and new_entries.empty?
@biffed.clear
end
end
end
class MaildirInfo < MailCheckInfo
def initialize(dir)
full_path = File.expand_path(dir).freeze
@glob_patterns = [
File.expand_path("cur/*,", dir).freeze,
File.expand_path("new/*", dir).freeze,
].freeze
super()
end
end
class MhInfo < MailCheckInfo
def initialize(dir)
full_path = File.expand_path("#{dir}/*")
@glob_patterns = [
full_path.freeze,
].freeze
super()
end
end
def bot_initialize
@on_timer = nil
p [:MailCheckBot, :bot_initialize] if $DEBUG
@m_infos = {}
@bot_config.freeze
@channels = @bot_config[:channels].collect do |ch|
NKF.nkf("-j -m0", ch).freeze
end.freeze
@template = (@bot_config[:template] || "biff: %{subject}s").freeze
if @bot_config.key?(:Maildir)
if @bot_config.key?(:mh_dir)
raise Error, "both :Maildir and :mh_dir found in #{@bot_config.inspect}"
end
@bot_config[:Maildir].each do |dir|
raise Error, "duplicated Maildir: #{dir}" if @m_infos.key?(dir)
@m_infos[dir] = MaildirInfo.new(dir)
end
elsif @bot_config.key?(:mh_dir)
@bot_config[:mh_dir].each do |dir|
raise Error, "duplicated MH dir: #{dir}" if @m_infos.key?(dir)
@m_infos[dir] = MhInfo.new(dir)
end
else
raise Error, ":Maildir or :mh_dir not found in #{@bot_config.inspect}"
end
end
def bot_state
keys = @m_infos.keys
dir, = File.basename(keys[0])
if keys.size > 1
dir = "#{dir}, ..."
end
"\#<#{self.class}: #{dir} -> #{@channels.join(' ')}>"
end
def on_timer t
p [:MailCheckBot, :on_timer, t] if $DEBUG
if @on_timer
$stderr.puts "MailCheckBot#on_timer duplicated (t=#{t} and old t=#{@on_timer})" if $DEBUG
return
end
@on_timer = t
mailcheck
ensure
@on_timer = false
end
def mailcheck
@m_infos.each do |dir, m_info|
m_info.update_entries do |fields|
bark(apply_template(fields))
end
end
end
def apply_template(fields)
msg = @template.dup
msg.gsub!(/%\{([a-z0-9_\-]+)\}([sd])/i) do
field_name, field_type = $1, $2
field_name.downcase!
case field_type
when 's'
fields[field_name].to_s
when 'd'
fields[field_name].to_i.to_s
else
"(field type bug: `#{field_type}')"
end
end
NKF.nkf("-j -m0", msg).freeze
end
def bark(msg)
@channels.each do |ch|
send_notice(ch, msg)
end
end
end