]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/core/irclog.rb
auth core botmodule: advertise and act on mismatched master password
[user/henk/code/ruby/rbot.git] / lib / rbot / core / irclog.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: rbot IRC logging facilities
5 #
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
7
8 class IrcLogModule < CoreBotModule
9
10   Config.register Config::IntegerValue.new('irclog.max_open_files',
11     :default => 20, :validate => Proc.new { |v| v > 0 },
12     :desc => "Maximum number of irclog files to keep open at any one time.")
13   Config.register Config::ArrayValue.new('irclog.no_log',
14     :default => [], :on_change => Proc.new { |bot, v|
15       bot.plugins.delegate 'event_irclog_list_changed', v, bot.config['irclog.do_log']
16     },
17     :desc => "List of channels and nicks for which logging is disabled. IRC patterns can be used too.")
18   Config.register Config::ArrayValue.new('irclog.do_log',
19     :default => [], :on_change => Proc.new { |bot, v|
20       bot.plugins.delegate 'event_irclog_list_changed', bot.config['irclog.no_log'], v
21     },
22     :desc => "List of channels and nicks for which logging is enabled. IRC patterns can be used too. This can be used to override wide patters in irclog.no_log")
23
24   attr :nolog_rx, :dolog_rx
25   def initialize
26     super
27     @queue = Queue.new
28     @thread = Thread.new { loggers_thread }
29     @logs = Hash.new
30     Dir.mkdir("#{@bot.botclass}/logs") unless File.exist?("#{@bot.botclass}/logs")
31     event_irclog_list_changed(@bot.config['irclog.no_log'], @bot.config['irclog.do_log'])
32   end
33
34   def can_log_on(where)
35     return true if @dolog_rx and where.match @dolog_rx
36     return false if @nolog_rx and where.match @nolog_rx
37     return true
38   end
39
40   def event_irclog_list_changed(nolist, dolist)
41     @nolog_rx = nolist.empty? ? nil : Regexp.union(*(nolist.map { |r| r.to_irc_regexp }))
42     debug "no log: #{@nolog_rx}"
43     @dolog_rx = dolist.empty? ? nil : Regexp.union(*(dolist.map { |r| r.to_irc_regexp }))
44     debug "do log: #{@dolog_rx}"
45     @logs.inject([]) { |ar, kv|
46       ar << kv.first unless can_log_on(kv.first)
47       ar
48     }.each { |w| logfile_close(w, 'logging disabled here') }
49   end
50
51   def logfile_close(where_str, reason = 'unknown reason')
52     f = @logs.delete(where_str) or return
53     stamp = Time.now.strftime '%Y/%m/%d %H:%M:%S'
54     f[1].puts "[#{stamp}] @ Log closed by #{@bot.myself.nick} (#{reason})"
55     f[1].close
56   end
57
58   # log IRC-related message +message+ to a file determined by +where+.
59   # +where+ can be a channel name, or a nick for private message logging
60   def irclog(message, where="server")
61     @queue.push [message, where]
62   end
63
64   def cleanup
65     @queue << nil
66     @thread.join
67     @thread = nil
68   end
69
70   def sent(m)
71     case m
72     when NoticeMessage
73       irclog "-#{m.source}- #{m.message}", m.target
74     when PrivMessage
75       logtarget = who = m.target
76       if m.ctcp
77         case m.ctcp.intern
78         when :ACTION
79           irclog "* #{m.source} #{m.logmessage}", logtarget
80         when :VERSION
81           irclog "@ #{m.source} asked #{who} about version info", logtarget
82         when :SOURCE
83           irclog "@ #{m.source} asked #{who} about source info", logtarget
84         when :PING
85           irclog "@ #{m.source} pinged #{who}", logtarget
86         when :TIME
87           irclog "@ #{m.source} asked #{who} what time it is", logtarget
88         else
89           irclog "@ #{m.source} asked #{who} about #{[m.ctcp, m.message].join(' ')}", logtarget
90         end
91       else
92         irclog "<#{m.source}> #{m.logmessage}", logtarget
93       end
94     when QuitMessage
95       m.was_on.each { |ch|
96         irclog "@ quit (#{m.message})", ch
97       }
98     end
99   end
100
101   def welcome(m)
102     irclog "joined server #{m.server} as #{m.target}", "server"
103   end
104
105   def listen(m)
106     case m
107     when PrivMessage
108       method = 'log_message'
109     else
110       method = 'log_' + m.class.name.downcase.match(/^irc::(\w+)message$/).captures.first
111     end
112     if self.respond_to?(method)
113       self.__send__(method, m)
114     else
115       warning "unhandled logging for #{m.pretty_inspect} (no such method #{method})"
116       unknown_message(m)
117     end
118   end
119
120   def log_message(m)
121     if m.ctcp
122       who = m.private? ? "me" : m.target
123       logtarget = m.private? ? m.source : m.target
124       case m.ctcp.intern
125       when :ACTION
126         if m.public?
127           irclog "* #{m.source} #{m.logmessage}", m.target
128         else
129           irclog "* #{m.source}(#{m.sourceaddress}) #{m.logmessage}", m.source
130         end
131       when :VERSION
132         irclog "@ #{m.source} asked #{who} about version info", logtarget
133       when :SOURCE
134         irclog "@ #{m.source} asked #{who} about source info", logtarget
135       when :PING
136         irclog "@ #{m.source} pinged #{who}", logtarget
137       when :TIME
138         irclog "@ #{m.source} asked #{who} what time it is", logtarget
139       else
140         irclog "@ #{m.source} asked #{who} about #{[m.ctcp, m.message].join(' ')}", logtarget
141       end
142     else
143       if m.public? 
144         irclog "<#{m.source}> #{m.logmessage}", m.target
145       else
146         irclog "<#{m.source}(#{m.sourceaddress})> #{m.logmessage}", m.source
147       end
148     end
149   end
150
151   def log_notice(m)
152     if m.private?
153       irclog "-#{m.source}(#{m.sourceaddress})- #{m.logmessage}", m.source
154     else
155       irclog "-#{m.source}- #{m.logmessage}", m.target
156     end
157   end
158
159   def motd(m)
160     m.message.each_line { |line|
161       irclog "MOTD: #{line}", "server"
162     }
163   end
164
165   def log_nick(m)
166     m.is_on.each { |ch|
167       irclog "@ #{m.oldnick} is now known as #{m.newnick}", ch
168     }
169   end
170
171   def log_quit(m)
172     m.was_on.each { |ch|
173       irclog "@ Quit: #{m.source}: #{m.logmessage}", ch
174     }
175   end
176
177   def modechange(m)
178     irclog "@ Mode #{m.logmessage} by #{m.source}", m.target
179   end
180
181   def log_join(m)
182     if m.address?
183       debug "joined channel #{m.channel}"
184       irclog "@ Joined channel #{m.channel}", m.channel
185     else
186       irclog "@ #{m.source} joined channel #{m.channel}", m.channel
187     end
188   end
189
190   def log_part(m)
191     if(m.address?)
192       debug "left channel #{m.channel}"
193       irclog "@ Left channel #{m.channel} (#{m.logmessage})", m.channel
194     else
195       irclog "@ #{m.source} left channel #{m.channel} (#{m.logmessage})", m.channel
196     end
197   end
198
199   def log_kick(m)
200     if(m.address?)
201       debug "kicked from channel #{m.channel}"
202       irclog "@ You have been kicked from #{m.channel} by #{m.source} (#{m.logmessage})", m.channel
203     else
204       irclog "@ #{m.target} has been kicked from #{m.channel} by #{m.source} (#{m.logmessage})", m.channel
205     end
206   end
207
208   # def log_invite(m)
209   #   # TODO
210   # end
211
212   def log_topic(m)
213     case m.info_or_set
214     when :set
215       if m.source == @bot.myself
216         irclog "@ I set topic \"#{m.topic}\"", m.channel
217       else
218         irclog "@ #{m.source} set topic \"#{m.topic}\"", m.channel
219       end
220     when :info
221       topic = m.channel.topic
222       irclog "@ Topic is \"#{m.topic}\"", m.channel
223       irclog "@ Topic set by #{topic.set_by} on #{topic.set_on}", m.channel
224     end
225   end
226
227   # def names(m)
228   #   # TODO
229   # end
230
231   def unknown_message(m)
232     irclog m.logmessage, ".unknown"
233   end
234
235   protected
236   def loggers_thread
237     ls = nil
238     debug 'loggers_thread starting'
239     while ls = @queue.pop
240       message, where = ls
241       message = message.chomp
242       now = Time.now
243       stamp = now.strftime("%Y/%m/%d %H:%M:%S")
244       if where.class <= Server
245         where_str = "server"
246       else
247         where_str = where.downcase.gsub(/[:!?$*()\/\\<>|"']/, "_")
248       end
249       return unless can_log_on(where_str)
250       unless @logs.has_key? where_str
251         if @logs.size > @bot.config['irclog.max_open_files']
252           @logs.keys.sort do |a, b|
253             @logs[a][0] <=> @logs[b][0]
254           end.slice(0, @logs.size - @bot.config['irclog.max_open_files']).each do |w|
255             logfile_close w, "idle since #{@logs[w][0]}"
256           end
257         end
258         f = File.new("#{@bot.botclass}/logs/#{where_str}", "a")
259         f.sync = true
260         f.puts "[#{stamp}] @ Log started by #{@bot.myself.nick}"
261         @logs[where_str] = [now, f]
262       end
263       @logs[where_str][1].puts "[#{stamp}] #{message}"
264       @logs[where_str][0] = now
265       #debug "[#{stamp}] <#{where}> #{message}"
266     end
267     @logs.keys.each { |w| logfile_close(w, 'rescan or shutdown') }
268     debug 'loggers_thread terminating'
269   end
270 end
271
272 ilm = IrcLogModule.new
273 ilm.priority = -1
274