define_structure :Saw, :nick, :time, :type, :where, :message
class SeenPlugin < Plugin
+
+ MSG_PUBLIC = N_("saying \"%{message}\" in %{where}")
+ MSG_ACTION = N_("doing *%{nick} %{message}* in %{where}")
+ MSG_NICK = N_("changing nick from %{nick} to %{message}")
+ MSG_PART = N_("leaving %{where} (%{message})")
+ MSG_PART_EMPTY = N_("leaving %{where}")
+ MSG_JOIN = N_("joining %{where}")
+ MSG_QUIT = N_("quitting IRC (%{message})")
+ MSG_TOPIC = N_("changing the topic of %{where} to \"%{message}\"")
+
+ CHANPRIV_CHAN = N_("a private channel")
+ CHANPRIV_MSG_TOPIC = N_("changing the topic of %{where}")
+
+ MSGPRIV_MSG_PUBLIC = N_("speaking in %{where}")
+ MSGPRIV_MSG_ACTION = N_("doing something in %{where}")
+
+ FORMAT_NORMAL = N_("%{nick} was last seen %{when}, %{doing}")
+ FORMAT_WITH_BEFORE = N_("%{nick} was last seen %{when}, %{doing} and %{time} before %{did_before}")
+
+ Config.register Config::IntegerValue.new('seen.max_results',
+ :default => 3, :validate => Proc.new{|v| v >= 0},
+ :desc => _("Maximum number of seen users to return in search (0 = no limit)."))
+ Config.register Config::ArrayValue.new('seen.ignore_patterns',
+ :default => [ "^no u$" ],
+ :desc => _("Strings/regexes that you'd like to ignore for 'last message' purposes"))
+
def help(plugin, topic="")
- "seen <nick> => have you seen, or when did you last see <nick>"
+ _("seen <nick> => have you seen, or when did you last see <nick>")
end
-
+
def privmsg(m)
unless(m.params =~ /^(\S)+$/)
m.reply "incorrect usage: " + help(m.plugin)
if @registry.has_key?(m.params)
m.reply seen(@registry[m.params])
else
- m.reply "nope!"
+ rx = Regexp.new(m.params, true)
+ num_matched = 0
+ @registry.each {|nick, saw|
+ if nick.match(rx)
+ m.reply seen(saw)
+ num_matched += 1
+ break if num_matched == @bot.config['seen.max_results']
+ end
+ }
+
+ m.reply _("nope!") if num_matched.zero?
end
end
case m
when PrivMessage
return if m.private?
+ @bot.config['seen.ignore_patterns'].each { |regex|
+ return if m.message =~ /#{regex}/
+ }
+
type = m.action? ? 'ACTION' : 'PUBLIC'
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, now, type,
- m.target.to_s, m.message.dup)
+ store m, Saw.new(m.sourcenick.dup, now, type,
+ m.target.to_s, m.message.dup)
when QuitMessage
return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, now, "QUIT",
- nil, m.message.dup)
+ store m, Saw.new(m.sourcenick.dup, now, "QUIT",
+ nil, m.message.dup)
when NickMessage
return if m.address?
- saw = Saw.new(m.oldnick, now, "NICK", nil, m.newnick)
- @registry[m.oldnick] = saw
- @registry[m.newnick] = saw
+ store m, Saw.new(m.oldnick, now, "NICK", nil, m.newnick)
when PartMessage
return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "PART",
- m.target.to_s, m.message.dup)
+ store m, Saw.new(m.sourcenick.dup, Time.new, "PART",
+ m.target.to_s, m.message.dup)
when JoinMessage
return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "JOIN",
- m.target.to_s, m.message.dup)
+ store m, Saw.new(m.sourcenick.dup, Time.new, "JOIN",
+ m.target.to_s, m.message.dup)
when TopicMessage
- return if m.address?
- @registry[m.sourcenick] = Saw.new(m.sourcenick.dup, Time.new, "TOPIC",
- m.target.to_s, m.message.dup)
+ return if m.address? or m.info_or_set == :info
+ store m, Saw.new(m.sourcenick.dup, Time.new, "TOPIC",
+ m.target.to_s, m.message.dup)
end
end
-
- def seen(saw)
- ret = "#{saw.nick} was last seen "
+
+ def seen(reg)
+ saw = case reg
+ when Struct::Saw
+ reg # for backwards compatibility
+ when Array
+ reg.last
+ end
+
+ if reg.kind_of? Array
+ before = reg.first
+ end
+
+ # TODO: a message should not be disclosed if:
+ # - it was said in a channel that was/is invite-only, private or secret
+ # - UNLESS the requester is also in the channel now, or the request is made
+ # in the channel?
+ msg_privacy = false
+ # TODO: a channel or it's topic should not be disclosed if:
+ # - the channel was/is private or secret
+ # - UNLESS the requester is also in the channel now, or the request is made
+ # in the channel?
+ chan_privacy = false
+
+ # What should be displayed for channel?
+ where = chan_privacy ? _(CHANPRIV_CHAN) : saw.where
+
+ formats = {
+ :normal => _(FORMAT_NORMAL),
+ :with_before => _(FORMAT_WITH_BEFORE)
+ }
+
+ if before && [:PART, :QUIT].include?(saw.type.to_sym) &&
+ [:PUBLIC, :ACTION].include?(before.type.to_sym)
+ # TODO see chan_privacy
+ prev_chan_privacy = false
+ prev_where = prev_chan_privacy ? _(CHANPRIV_CHAN) : before.where
+ did_before = case before.type.to_sym
+ when :PUBLIC
+ _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC)
+ when :ACTION
+ _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION)
+ end % {
+ :nick => saw.nick,
+ :message => before.message,
+ :where => prev_where
+ }
+
+ format = :with_before
+
+ time_diff = saw.time - before.time
+ if time_diff < 300
+ time_before = _("a moment")
+ elsif time_diff < 3600
+ time_before = _("a while")
+ else
+ format = :normal
+ end
+ else
+ format = :normal
+ end
+
+ nick = saw.nick
ago = Time.new - saw.time
-
+
if (ago.to_i == 0)
- ret << "just now, "
+ time = _("just now")
else
- ret << Utils.secs_to_string(ago) + " ago, "
+ time = _("%{time} ago") % { :time => Utils.secs_to_string(ago) }
end
- case saw.type.to_sym
+ doing = case saw.type.to_sym
when :PUBLIC
- ret << "saying #{saw.message}"
+ _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC)
when :ACTION
- ret << "doing #{saw.nick} #{saw.message}"
+ _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION)
when :NICK
- ret << "changing nick from #{saw.nick} to #{saw.message}"
+ _(MSG_NICK)
when :PART
- ret << "leaving #{saw.where}"
+ if saw.message.empty?
+ _(MSG_PART_EMPTY)
+ else
+ _(MSG_PART)
+ end
when :JOIN
- ret << "joining #{saw.where}"
+ _(MSG_JOIN)
when :QUIT
- ret << "quitting IRC (#{saw.message})"
+ _(MSG_QUIT)
when :TOPIC
- ret << "changing the topic of #{saw.where} to #{saw.message}"
+ _(chan_privacy ? CHANPRIV_MSG_TOPIC : MSG_TOPIC)
+ end % { :message => saw.message, :where => where, :nick => saw.nick }
+
+ case format
+ when :normal
+ formats[:normal] % {
+ :nick => saw.nick,
+ :when => time,
+ :doing => doing,
+ }
+ when :with_before
+ formats[:with_before] % {
+ :nick => saw.nick,
+ :when => time,
+ :doing => doing,
+ :time => time_before,
+ :did_before => did_before
+ }
end
end
-
+
+ def store(m, saw)
+ # TODO: we need to store the channel state INVITE/SECRET/PRIVATE here, in
+ # some symbolic form, so that we know the prior state of the channel when
+ # it comes time to display.
+ reg = @registry[saw.nick]
+
+ if reg && reg.is_a?(Array)
+ reg.shift if reg.size > 1
+ reg.push(saw)
+ else
+ reg = [saw]
+ end
+
+ if m.is_a? NickMessage
+ @registry[m.newnick] = reg
+ end
+
+ @registry[saw.nick] = reg
+ end
end
plugin = SeenPlugin.new
plugin.register("seen")