6 # Keep a database of who last said/did what
8 define_structure :Saw, :nick, :time, :type, :where, :message
10 class SeenPlugin < Plugin
12 MSG_PUBLIC = N_("saying \"%{message}\" in %{where}")
13 MSG_ACTION = N_("doing *%{nick} %{message}* in %{where}")
14 MSG_NICK = N_("changing nick from %{nick} to %{message}")
15 MSG_PART = N_("leaving %{where} (%{message})")
16 MSG_PART_EMPTY = N_("leaving %{where}")
17 MSG_JOIN = N_("joining %{where}")
18 MSG_QUIT = N_("quitting IRC (%{message})")
19 MSG_TOPIC = N_("changing the topic of %{where} to \"%{message}\"")
21 CHANPRIV_CHAN = N_("a private channel")
22 CHANPRIV_MSG_TOPIC = N_("changing the topic of %{where}")
24 MSGPRIV_MSG_PUBLIC = N_("speaking in %{where}")
25 MSGPRIV_MSG_ACTION = N_("doing something in %{where}")
27 FORMAT_NORMAL = N_("%{nick} was last seen %{when}, %{doing}")
28 FORMAT_WITH_BEFORE = N_("%{nick} was last seen %{when}, %{doing} and %{time} before %{did_before}")
30 Config.register Config::IntegerValue.new('seen.max_results',
31 :default => 3, :validate => Proc.new{|v| v >= 0},
32 :desc => _("Maximum number of seen users to return in search (0 = no limit)."))
33 Config.register Config::ArrayValue.new('seen.ignore_patterns',
34 :default => [ "^no u$" ],
35 :desc => _("Strings/regexes that you'd like to ignore for 'last message' purposes"))
37 def help(plugin, topic="")
38 _("seen <nick> => have you seen, or when did you last see <nick>")
42 unless(m.params =~ /^(\S)+$/)
43 m.reply "incorrect usage: " + help(m.plugin)
47 m.params.gsub!(/\?$/, "")
49 if @registry.has_key?(m.params)
50 m.reply seen(@registry[m.params])
52 rx = Regexp.new(m.params, true)
54 @registry.each {|nick, saw|
58 break if num_matched == @bot.config['seen.max_results']
62 m.reply _("nope!") if num_matched.zero?
67 return unless m.source
68 # keep database up to date with who last said what
73 @bot.config['seen.ignore_patterns'].each { |regex|
74 return if m.message =~ /#{regex}/
77 type = m.action? ? 'ACTION' : 'PUBLIC'
78 store m, Saw.new(m.sourcenick.dup, now, type,
79 m.target.to_s, m.message.dup)
82 store m, Saw.new(m.sourcenick.dup, now, "QUIT",
86 store m, Saw.new(m.oldnick, now, "NICK", nil, m.newnick)
89 store m, Saw.new(m.sourcenick.dup, Time.new, "PART",
90 m.target.to_s, m.message.dup)
93 store m, Saw.new(m.sourcenick.dup, Time.new, "JOIN",
94 m.target.to_s, m.message.dup)
96 return if m.address? or m.info_or_set == :info
97 store m, Saw.new(m.sourcenick.dup, Time.new, "TOPIC",
98 m.target.to_s, m.message.dup)
105 reg # for backwards compatibility
110 if reg.kind_of? Array
114 # TODO: a message should not be disclosed if:
115 # - it was said in a channel that was/is invite-only, private or secret
116 # - UNLESS the requester is also in the channel now, or the request is made
119 # TODO: a channel or it's topic should not be disclosed if:
120 # - the channel was/is private or secret
121 # - UNLESS the requester is also in the channel now, or the request is made
125 # What should be displayed for channel?
126 where = chan_privacy ? _(CHANPRIV_CHAN) : saw.where
129 :normal => _(FORMAT_NORMAL),
130 :with_before => _(FORMAT_WITH_BEFORE)
133 if before && [:PART, :QUIT].include?(saw.type.to_sym) &&
134 [:PUBLIC, :ACTION].include?(before.type.to_sym)
135 # TODO see chan_privacy
136 prev_chan_privacy = false
137 prev_where = prev_chan_privacy ? _(CHANPRIV_CHAN) : before.where
138 did_before = case before.type.to_sym
140 _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC)
142 _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION)
145 :message => before.message,
149 format = :with_before
151 time_diff = saw.time - before.time
153 time_before = _("a moment")
154 elsif time_diff < 3600
155 time_before = _("a while")
164 ago = Time.new - saw.time
169 time = _("%{time} ago") % { :time => Utils.secs_to_string(ago) }
172 doing = case saw.type.to_sym
174 _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC)
176 _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION)
180 if saw.message.empty?
190 _(chan_privacy ? CHANPRIV_MSG_TOPIC : MSG_TOPIC)
191 end % { :message => saw.message, :where => where, :nick => saw.nick }
201 formats[:with_before] % {
205 :time => time_before,
206 :did_before => did_before
212 # TODO: we need to store the channel state INVITE/SECRET/PRIVATE here, in
213 # some symbolic form, so that we know the prior state of the channel when
214 # it comes time to display.
215 reg = @registry[saw.nick]
217 if reg && reg.is_a?(Array)
218 reg.shift if reg.size > 1
224 if m.is_a? NickMessage
225 @registry[m.newnick] = reg
228 @registry[saw.nick] = reg
231 plugin = SeenPlugin.new
232 plugin.register("seen")