#-- vim:sw=2:et #++ # # :title: Seen Plugin # # Keep a database of who last said/did what 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 => have you seen, or when did you last see ") end def privmsg(m) unless(m.params =~ /^(\S)+$/) m.reply "incorrect usage: " + help(m.plugin) return end m.params.gsub!(/\?$/, "") if @registry.has_key?(m.params) m.reply seen(@registry[m.params]) else 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 def listen(m) return unless m.source # keep database up to date with who last said what now = Time.new 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' store m, Saw.new(m.sourcenick.dup, now, type, m.target.to_s, m.message.dup) when QuitMessage return if m.address? store m, Saw.new(m.sourcenick.dup, now, "QUIT", nil, m.message.dup) when NickMessage return if m.address? store m, Saw.new(m.oldnick, now, "NICK", nil, m.newnick) when PartMessage return if m.address? store m, Saw.new(m.sourcenick.dup, Time.new, "PART", m.target.to_s, m.message.dup) when JoinMessage return if m.address? store m, Saw.new(m.sourcenick.dup, Time.new, "JOIN", m.target.to_s, m.message.dup) when TopicMessage 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(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) time = _("just now") else time = _("%{time} ago") % { :time => Utils.secs_to_string(ago) } end doing = case saw.type.to_sym when :PUBLIC _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC) when :ACTION _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION) when :NICK _(MSG_NICK) when :PART if saw.message.empty? _(MSG_PART_EMPTY) else _(MSG_PART) end when :JOIN _(MSG_JOIN) when :QUIT _(MSG_QUIT) when :TOPIC _(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")