]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/seen.rb
84a2486e7ae578d4b1e87a171272b8523ffbb441
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / seen.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Seen Plugin
5 #
6 # Keep a database of who last said/did what
7
8 define_structure :Saw, :nick, :time, :type, :where, :message
9
10 class SeenPlugin < Plugin
11
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}\"")
20
21   CHANPRIV_CHAN      = N_("a private channel")
22   CHANPRIV_MSG_TOPIC  = N_("changing the topic of %{where}")
23
24   MSGPRIV_MSG_PUBLIC = N_("speaking in %{where}")
25   MSGPRIV_MSG_ACTION = N_("doing something in %{where}")
26
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}")
29
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
34   def help(plugin, topic="")
35     _("seen <nick> => have you seen, or when did you last see <nick>")
36   end
37
38   def privmsg(m)
39     unless(m.params =~ /^(\S)+$/)
40       m.reply "incorrect usage: " + help(m.plugin)
41       return
42     end
43
44     m.params.gsub!(/\?$/, "")
45
46     if @registry.has_key?(m.params)
47       m.reply seen(@registry[m.params])
48     else
49       rx = Regexp.new(m.params, true)
50       num_matched = 0
51       @registry.each {|nick, saw|
52         if nick.match(rx)
53           m.reply seen(saw)
54           num_matched += 1
55           break if num_matched == @bot.config['seen.max_results']
56         end
57       }
58
59       m.reply _("nope!") if num_matched.zero?
60     end
61   end
62
63   def listen(m)
64     return unless m.source
65     # keep database up to date with who last said what
66     now = Time.new
67     case m
68     when PrivMessage
69       return if m.private?
70       type = m.action? ? 'ACTION' : 'PUBLIC'
71       store m, Saw.new(m.sourcenick.dup, now, type,
72                        m.target.to_s, m.message.dup)
73     when QuitMessage
74       return if m.address?
75       store m, Saw.new(m.sourcenick.dup, now, "QUIT",
76                        nil, m.message.dup)
77     when NickMessage
78       return if m.address?
79       store m, Saw.new(m.oldnick, now, "NICK", nil, m.newnick)
80     when PartMessage
81       return if m.address?
82       store m, Saw.new(m.sourcenick.dup, Time.new, "PART",
83                        m.target.to_s, m.message.dup)
84     when JoinMessage
85       return if m.address?
86       store m, Saw.new(m.sourcenick.dup, Time.new, "JOIN",
87                        m.target.to_s, m.message.dup)
88     when TopicMessage
89       return if m.address? or m.info_or_set == :info
90       store m, Saw.new(m.sourcenick.dup, Time.new, "TOPIC",
91                        m.target.to_s, m.message.dup)
92     end
93   end
94
95   def seen(reg)
96     saw = case reg
97     when Struct::Saw
98       reg # for backwards compatibility
99     when Array
100       reg.last
101     end
102
103     if reg.kind_of? Array
104       before = reg.first
105     end
106
107     # TODO: a message should not be disclosed if:
108     # - it was said in a channel that was/is invite-only, private or secret
109     # - UNLESS the requester is also in the channel now, or the request is made
110     #   in the channel?
111     msg_privacy = false
112     # TODO: a channel or it's topic should not be disclosed if:
113     # - the channel was/is private or secret
114     # - UNLESS the requester is also in the channel now, or the request is made
115     #   in the channel?
116     chan_privacy = false
117
118     # What should be displayed for channel?
119     where = chan_privacy ? _(CHANPRIV_CHAN) : saw.where
120
121     formats = {
122       :normal      => _(FORMAT_NORMAL),
123       :with_before => _(FORMAT_WITH_BEFORE)
124     }
125
126     if before && [:PART, :QUIT].include?(saw.type.to_sym) &&
127        [:PUBLIC, :ACTION].include?(before.type.to_sym)
128       # TODO see chan_privacy
129       prev_chan_privacy = false
130       prev_where = prev_chan_privacy ? _(CHANPRIV_CHAN) : before.where
131       did_before = case before.type.to_sym
132       when :PUBLIC
133         _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC)
134       when :ACTION
135         _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION)
136       end % {
137         :nick => saw.nick,
138         :message => before.message,
139         :where => prev_where
140       }
141
142       format = :with_before
143
144       time_diff = saw.time - before.time
145       if time_diff < 300
146         time_before = _("a moment")
147       elsif time_diff < 3600
148         time_before = _("a while")
149       else
150         format = :normal
151       end
152     else
153       format = :normal
154     end
155
156     nick = saw.nick
157     ago = Time.new - saw.time
158
159     if (ago.to_i == 0)
160       time = _("just now")
161     else
162       time = _("%{time} ago") % { :time => Utils.secs_to_string(ago) }
163     end
164
165     doing = case saw.type.to_sym
166     when :PUBLIC
167       _(msg_privacy ? MSGPRIV_MSG_PUBLIC : MSG_PUBLIC)
168     when :ACTION
169       _(msg_privacy ? MSGPRIV_MSG_ACTION : MSG_ACTION)
170     when :NICK
171       _(MSG_NICK)
172     when :PART
173       if saw.message.empty?
174         _(MSG_PART_EMPTY)
175       else
176         _(MSG_PART)
177       end
178     when :JOIN
179       _(MSG_JOIN)
180     when :QUIT
181       _(MSG_QUIT)
182     when :TOPIC
183       _(chan_privacy ? CHANPRIV_MSG_TOPIC : MSG_TOPIC)
184     end % { :message => saw.message, :where => where, :nick => saw.nick }
185
186     case format
187     when :normal
188       formats[:normal] % {
189         :nick  => saw.nick,
190         :when  => time,
191         :doing => doing,
192       }
193     when :with_before
194       formats[:with_before] % {
195         :nick  => saw.nick,
196         :when  => time,
197         :doing => doing,
198         :time  => time_before,
199         :did_before => did_before
200       }
201     end
202   end
203
204   def store(m, saw)
205     # TODO: we need to store the channel state INVITE/SECRET/PRIVATE here, in
206     # some symbolic form, so that we know the prior state of the channel when
207     # it comes time to display.
208     reg = @registry[saw.nick]
209
210     if reg && reg.is_a?(Array)
211       reg.shift if reg.size > 1
212       reg.push(saw)
213     else
214       reg = [saw]
215     end
216
217     if m.is_a? NickMessage
218       @registry[m.newnick] = reg
219     end
220
221     @registry[saw.nick] = reg
222   end
223 end
224 plugin = SeenPlugin.new
225 plugin.register("seen")