7
__copyright__ = 'this file is in the public domain'
9
from gozerbot.generic import rlog, handle_exception, elapsedstring, dmy, \
10
hourmin, lockdec, strtotime
11
from gozerbot.commands import cmnds
12
from gozerbot.examples import examples
13
from gozerbot.callbacks import callbacks, jcallbacks
14
from gozerbot.plughelp import plughelp
15
from gozerbot.monitor import saymonitor, jabbermonitor
16
from gozerbot.aliases import aliases, aliasdel
17
from gozerbot.users import users
18
import glob, re, thread, pickle, os, mmap, time
20
plughelp.add('log', 'logs related commands')
22
# check if logs dir exists if not create it
23
if not os.path.isdir('logs'):
26
loglock = thread.allocate_lock()
27
locked = lockdec(loglock)
31
""" hold handles to log files (per channel) """
33
def __init__(self, logdir):
38
self.lock = thread.allocate_lock()
41
loglistfile = open(self.logdir + os.sep + 'loglist', 'r')
42
self.loglist = pickle.load(loglistfile)
45
rlog(0, 'logs', 'loglist is %s' % str(self.loglist))
46
# open log files in append mode
47
for i in glob.glob(self.logdir + os.sep + '*.log'):
48
reresult = re.search('logs%s(.+)\.' % os.sep, i)
49
filename = reresult.group(1)
51
rlog(10, 'logs', "can't determine channel name")
53
logfile = open(i, 'a')
55
self.files[filename] = logfile
56
self.filenames[filename.lower()] = i
57
rlog(1, 'logs', 'adding file %s' % i)
59
rlog(10, 'logs', 'failed to open %s' % i)
65
picklefile = open(self.logdir + os.sep + 'loglist', 'w')
66
pickle.dump(self.loglist, picklefile)
67
rlog(0, 'logs', 'loglist saved')
72
""" close log files """
75
for i in self.files.keys():
76
rlog(10, 'logs', 'closing ' + str(i))
82
def size(self, channel):
83
""" get size of log file """
84
if channel[0] in ['#', '!', '&', '+']:
85
chan = channel[1:].lower()
87
chan = channel.lower()
89
size = os.stat(self.filenames[chan])[6]
94
def add(self, channel):
95
""" add logfile for channel """
96
if channel[0] in ['#', '!', '&', '+']:
97
chan = channel[1:].lower()
99
chan = channel.lower()
100
filename = self.logdir + os.sep + chan + '.log'
101
rlog(10, 'logs', 'adding logfile %s' % chan)
102
if self.files.has_key(chan):
103
rlog(10, 'logs', 'already opened file %s' % chan)
106
logfile = open(filename,'a')
107
self.files[chan] = logfile
108
self.filenames[chan] = filename
109
if channel not in self.loglist:
110
self.loglist.append(channel)
112
except Exception, ex:
116
def logbot(self, name, ttime, channel, txt):
117
""" log stuff spoken by the bot """
118
if channel not in self.loglist:
119
rlog(1, 'logs', "%s not in loglist" % channel)
121
if channel[0] in ['#', '!', '&', '+']:
122
chan = channel[1:].lower()
124
chan = channel.lower()
125
filename = self.logdir + os.sep + chan + '.log'
126
if not self.files.has_key(chan):
127
if not self.add(channel):
128
rlog(1, 'logs', "can't create logfile %s" % filename)
130
logfile = self.files[chan]
131
logitem = (name, ttime, 'bot', 'bot@bot', txt)
132
rlog(-1, 'logs', 'logging [%s] <%s> %s ==> %s' % (name, 'bot', txt, \
134
# write the data to file (comma seperated)
137
logfile.write('%s,%s,%s,%s,%s\n' % logitem)
142
def log(self, name, ttime, ievent):
144
if ievent.nick == ievent.bot.nick:
146
ievent.userhost = 'bot@bot'
147
channel = ievent.channel
148
if channel not in self.loglist:
149
rlog(1, 'logs', "%s not in loglist" % channel)
151
if channel[0] in ['#', '!', '&', '+']:
152
chan = channel[1:].lower()
154
chan = channel.lower()
155
filename = self.logdir + os.sep + chan + '.log'
156
if not self.files.has_key(chan):
157
if not self.add(ievent.channel):
158
rlog(1, 'logs', "can't create logfile %s" % filename)
160
# put ievent.cmnd as first txt
161
if ievent.cmnd == 'PRIVMSG' or ievent.cmnd == 'Message':
163
tmp = "CMND: " + ievent.txt
165
if ievent.txt.startswith('\001ACTION'):
166
txt = ievent.txt[7:-1]
167
tmp = "PRIVMSG: /me " + txt
169
tmp = "PRIVMSG: " + ievent.txt
170
elif ievent.cmnd == 'MODE':
171
tmp = "MODE: " + ievent.postfix
172
elif ievent.cmnd == 'PART':
173
tmp = "PART: " + ievent.channel
174
elif ievent.cmnd == 'JOIN':
175
tmp = "JOIN: " + ievent.channel
178
logfile = self.files[chan]
179
logitem = (name, ttime, ievent.nick, ievent.userhost, tmp)
180
rlog(-1, 'logs', 'logging [%s] <%s> %s ==> %s' % (name, ievent.nick, \
181
ievent.txt, filename))
182
# write the data to file (comma seperated)
185
logfile.write('%s,%s,%s,%s,%s\n' % logitem)
190
def getmmap(self, channel):
191
""" get an mmap of a channel .. as of 0.8 this just returns the
194
if channel[0] in ['#', '!', '&', '+']:
195
chan = channel[1:].lower()
197
chan = channel.lower()
199
filename = self.filenames[chan]
201
rlog(10, 'log', 'logging is not enabled in %s' % channel)
203
logfile = open(filename, 'r')
204
#size = self.size(channel)
207
#logmap = mmap.mmap(logfile.fileno(), size, mmap.MAP_SHARED, \
208
#access=mmap.ACCESS_READ)
211
def loop(self, logmap, nrtimes, func, withbot=False, withcmnd=False):
212
""" loop over the logmap """
218
if teller % 100000 == 0:
221
(botname, ttime, nick, userhost, txt) = \
222
line.strip().split(',', 4)
225
if not withbot and userhost == 'bot@bot':
227
if not withcmnd and txt.startswith('CMND:'):
230
res = func(botname, ttime, nick, userhost, txt)
235
if nrtimes and times > nrtimes:
239
def search(self, channel, what, nrtimes):
240
""" search through logfile """
244
andre = re.compile(' and ', re.I)
245
ands = re.split(andre, what)
246
nrtimes = int(nrtimes)
247
logmap = self.getmmap(channel)
251
def dofunc(botname, ttime, nick, userhost, txt):
252
""" search log line for matching txt """
254
if txt.find(j.strip()) == -1:
256
res.append((botname, ttime, nick, userhost, txt))
258
self.loop(logmap, nrtimes, dofunc)
262
def seen(self, channel, who):
263
""" get last line of who """
264
logmap = self.getmmap(channel)
270
who = who.replace('*', '.*')
271
rewho = re.compile(who)
272
def dofunc(botname, ttime, nick, userhost, txt):
273
""" add line that matches mask """
274
if re.match(rewho, nick.lower()):
275
result.append((botname, ttime, nick, userhost, txt))
277
def dofunc(botname, ttime, nick, userhost, txt):
278
""" add line that matches nick """
279
if nick.lower() == who:
280
result.append((botname, ttime, nick, userhost, txt))
281
self.loop(logmap, None, dofunc)
285
def back(self, channel, what, nrtimes):
286
""" search through the logs backwards """
290
andre = re.compile(' and ', re.I)
291
ands = re.split(andre, what)
292
nrtimes = int(nrtimes)
293
logmap = self.getmmap(channel)
296
def dofunc(botname, ttime, nick, userhost, txt):
297
""" add line that matches """
299
if txt.find(j.strip()) == -1:
301
res.append((botname, ttime, nick, userhost, txt))
303
self.loop(logmap, None, dofunc)
306
if len(res) > nrtimes:
307
result = res[len(res)-nrtimes:]
313
def bback(self, channel, what, nrtimes):
314
""" search through the bot logs backwards """
318
andre = re.compile(' and ', re.I)
319
ands = re.split(andre, what)
320
nrtimes = int(nrtimes)
321
logmap = self.getmmap(channel)
324
def dofunc(botname, ttime, nick, userhost, txt):
325
""" add line that matches """
329
if txt.find(j.strip()) == -1:
331
res.append((botname, ttime, nick, userhost, txt))
333
self.loop(logmap, None, dofunc, withbot=True)
336
if len(res) > nrtimes:
337
result = res[len(res)-nrtimes:]
343
def fromtime(self, channel, ftime):
344
""" returns log items from a certain time """
348
logmap = self.getmmap(channel)
351
def dofunc(botname, ttime, nick, userhost, txt):
352
""" add line that is older then given time """
353
logtime = float(ttime)
355
res.append((botname, ttime, nick, userhost, txt))
356
self.loop(logmap, None, dofunc)
359
def fromtimewithbot(self, channel, ftime):
360
""" returns log items from a certain time """
364
logmap = self.getmmap(channel)
367
def dofunc(botname, ttime, nick, userhost, txt):
368
""" add line that is older then given time including bot outout """
369
logtime = float(ttime)
371
res.append((botname, ttime, nick, userhost, txt))
372
self.loop(logmap, None, dofunc, withbot=True)
375
def linesback(self, channel, nrtimes):
376
""" return nr log lines back """
380
nrtimes = int(nrtimes)
381
logmap = self.getmmap(channel)
384
def dofunc(botname, ttime, nick, userhost, txt):
386
result.append((botname, ttime, nick, userhost, txt))
388
self.loop(logmap, None, dofunc)
389
if len(result) > nrtimes:
390
return result[len(result)-nrtimes:]
394
def linesbacknick(self, channel, who, nrtimes):
395
""" return log lines for nick """
399
nrtimes = int(nrtimes)
400
logmap = self.getmmap(channel)
403
def dofunc(botname, ttime, nick, userhost, txt):
404
""" add line said by nick """
405
if nick.lower() == who.lower():
406
result.insert(0, (botname, ttime, nick, userhost, txt))
408
self.loop(logmap, None, dofunc)
409
return result[:nrtimes]
411
def linesbacknicksearch(self, channel, who, searchitem, nrtimes):
412
""" search logs for lines said by who """
417
andre = re.compile(' and ', re.I)
418
ands = re.split(andre, searchitem)
419
nrtimes = int(nrtimes)
420
logmap = self.getmmap(channel)
423
def dofunc(botname, ttime, nick, userhost, txt):
424
""" add line that is said by nick containing txt """
425
if nick.lower() == who.lower():
428
if txt.find(j.strip()) == -1:
433
result.append((botname, ttime, nick, userhost, txt))
435
self.loop(logmap, None, dofunc)
436
if len(result) > nrtimes:
437
return result[len(result)-nrtimes:]
441
def lastspoke(self, channel, userhost):
442
""" return time of last line spoken by user with userhost """
446
logmap = self.getmmap(channel)
449
def dofunc(botname, ttime, nick, uh, txt):
450
""" add line that matches userhost """
452
result.append(int(float(ttime)))
453
self.loop(logmap, None, dofunc)
457
def lastspokelist(self, channel, userhost, nrtimes):
458
""" return time of last line spoken by user with userhost """
462
logmap = self.getmmap(channel)
465
def dofunc(botname, ttime, nick, uh, txt):
466
""" add line that matches userhost """
468
result.append(int(float(ttime)))
469
self.loop(logmap, None, dofunc)
471
return result[len(result)-nrtimes:]
473
def lastnicks(self, channel, nrtimes):
474
""" return the nicks of last said lines """
477
logmap = self.getmmap(channel)
481
def dofunc(botname, ttime, nick, userhost, txt):
484
self.loop(logmap, None, dofunc)
485
if len(result) > nrtimes:
486
result = result[len(result)-nrtimes:]
494
savelist.append(logs)
496
def prelogsay(botname, printto, txt, who, how, fromm):
497
""" determine if logsay callback needs to be called """
498
if printto in logs.loglist:
501
def cblogsay(botname, printto, txt, who, how, fromm):
502
""" log the bots output """
503
logs.logbot(botname, time.time(), printto, txt)
505
saymonitor.add('log', cblogsay, prelogsay)
507
def jprelogsay(botname, msg):
508
""" jabber log precondition """
510
to = str(msg.getTo())
513
if to in logs.loglist:
516
def jcblogsay(botname, msg):
517
""" log the jabber message """
519
to = str(msg.getTo())
523
logs.logbot(botname, time.time(), to, txt)
525
jabbermonitor.add('log', jcblogsay, jprelogsay)
527
def prelog(bot, ievent):
528
""" log pre condition """
529
if ievent.channel and ievent.channel in logs.loglist:
532
def logcb(bot, ievent):
533
""" callback that logs ievent """
534
logs.log(bot.name, time.time(), ievent)
536
callbacks.add('ALL', logcb, prelog)
537
jcallbacks.add('Message', logcb, prelog)
539
def handle_logon(bot, ievent):
540
""" log-on .. enable logging in channel the command was given in """
541
if not ievent.channel in logs.loglist:
542
logs.loglist.append(ievent.channel)
543
ievent.reply('logging enabled in %s' % ievent.channel)
545
ievent.reply('%s already in loglist' % ievent.channel)
547
cmnds.add('log-on', handle_logon, 'OPER')
548
examples.add('log-on', 'enable logging of the channel in which the command \
549
was given', 'log-on')
551
def handle_logoff(bot, ievent):
552
""" log-off .. disable logging in channel the command was given in"""
554
logs.loglist.remove(ievent.channel)
555
ievent.reply('logging disabled in %s' % ievent.channel)
557
ievent.reply('%s not in loglist' % ievent.channel)
559
cmnds.add('log-off', handle_logoff, 'OPER')
560
examples.add('log-off', 'disable logging of the channel in which the command \
561
was given', 'log-off')
563
def handle_loglist(bot, ievent):
564
""" log-list .. show list of channels that are being logged """
565
ievent.reply(str(logs.loglist))
567
cmnds.add('log-list', handle_loglist, 'OPER')
568
examples.add('log-list', 'show list of current logged channels', 'log-list')
570
def handle_loglen(bot, ievent):
571
""" log-len show length of logfile for channel in which command was \
573
if ievent.channel not in logs.loglist:
574
ievent.reply('logging not enabled in %s' % ievent.channel)
576
if ievent.channel[0] in ['#', '!', '&', '+']:
577
chan = ievent.channel[1:].lower()
579
chan = ievent.channel.lower()
580
ievent.reply(str(logs.size(chan)))
582
cmnds.add('log-len', handle_loglen, 'OPER')
583
examples.add('log-len', 'show size of log file of the channel the command \
584
was given in', 'log-len')
585
aliases.data['log-size'] = 'log-len'
587
def sayresult(bot, ievent, result, withbot=False):
588
""" reply with result """
594
if not withbot and i[3] == 'bot@bot':
596
if not withbot and ('PRIVMSG' in i[4] or 'CMND' in i[4]):
597
what = i[4].split(':', 1)[1].strip()
600
except (ValueError, IndexError):
602
res.append("[%s %s] <%s> %s" % (dmy(float(i[1])), \
603
hourmin(float(i[1])), i[2], what))
605
ievent.reply(res, dot=True)
607
ievent.reply("nothing found")
609
def handle_logback(bot, ievent):
610
""" log-back [<nrtimes>] <txt> .. search back through channel log """
611
if not ievent.channel:
612
ievent.reply("use chan <channelname> to set channel to search in")
614
if ievent.channel not in logs.loglist:
615
ievent.reply('logging not enabled in %s' % ievent.channel)
618
nrtimes = int(ievent.args[0])
619
txt = ' '.join(ievent.args[1:])
624
ievent.missing('<searchitem> or <nrtimes> <searchitem>')
626
result = logs.back(ievent.channel, txt, nrtimes)
627
sayresult(bot, ievent, result)
629
cmnds.add('log-back', handle_logback, ['USER', 'WEB', 'ANON'], speed=3)
630
examples.add('log-back', "log-back [<nrlinesback>] <txt> .. search backwards in log \
631
file of channel", '1) log-back http 2) log-back 1 http')
632
aliases.data['b'] = 'log-back'
633
aliases.data['back'] = 'log-back'
635
def handle_logbback(bot, ievent):
636
""" log-botback [<nrtimes>] <txt> ['back' <bytesback>] .. search back \
637
through bot output in the channel log """
638
if not ievent.channel:
639
ievent.reply("use chan <channelname> to set channel to search in")
641
if ievent.channel not in logs.loglist:
642
ievent.reply('logging not enabled in %s' % ievent.channel)
645
nrtimes = int(ievent.args[0])
646
txt = ' '.join(ievent.args[1:])
651
ievent.missing('<searchitem> or <nrtimes> <searchitem>')
653
result = logs.bback(ievent.channel, txt, nrtimes)
654
sayresult(bot, ievent, result, withbot=True)
656
cmnds.add('log-bback', handle_logbback, ['USER', 'WEB', 'ANON'], speed=3)
657
examples.add('bback', "log-bback [<nrtimes>] <txt> ['back' <bytesback>] .. \
658
search backwards in the bot output of the channel log file", '1) log-back \
659
http 2) log-back 1 http')
660
aliases.data['bback'] = 'log-bback'
662
def handle_logsearch(bot, ievent):
663
""" log-search [<nrtimes>] <txt> ['back' <bytesback>] .. search the log \
664
from the beginning """
665
if not ievent.channel:
666
ievent.reply("use chan <channelname> to set channel to search in")
668
if ievent.channel not in logs.loglist:
669
ievent.reply('logging not enabled in %s' % ievent.channel)
672
nrtimes = int(ievent.args[0])
673
txt = ' '.join(ievent.args[1:])
678
ievent.missing(' <searchitem> or <nrtimes> <searchitem>')
680
result = logs.search(ievent.channel, txt, nrtimes)
681
sayresult(bot, ievent, result)
683
cmnds.add('log-search', handle_logsearch, ['USER', 'WEB', 'ANON'], speed=3)
684
examples.add('log-search', 'log-search [<nrtimes>] <txt> .. search the log \
685
from the beginning', '1) log-search http 2) log-search 10 http')
688
def handle_loglast(bot, ievent):
689
""" log-last [<nr>] [<nick>] [<txt>] .. search log for last lines of
690
<nick> containing <txt> """
691
if ievent.channel not in logs.loglist:
692
ievent.reply('logging not enabled in %s' % ievent.channel)
696
nrlines = int(ievent.args[0])
698
except (IndexError, ValueError):
701
nick, txt = ievent.args
705
nick = ievent.args[0]
709
result = logs.linesbacknicksearch(ievent.channel, nick, txt, \
712
result = logs.linesbacknick(ievent.channel, nick, nrlines)
714
result = logs.linesback(ievent.channel, nrlines)
716
sayresult(bot, ievent, result)
718
ievent.reply('no result found')
720
cmnds.add('log-last', handle_loglast, ['USER', 'WEB', 'ANON'], speed=3)
721
examples.add('log-last', 'log-last [<nr>] [<nick>] [<txt>] .. show lastlines \
722
of channel or user', '1) log-last dunker 2) log-last 5 dunker 3) log-last \
723
dunker http 4) log-last 5 dunker http')
724
aliases.data['last'] = 'log-last'
726
def handle_logtime(bot, ievent):
727
""" show log from a certain time """
729
ievent.reply('log plugin is not enabled')
731
if ievent.channel not in logs.loglist:
732
ievent.reply('logging is not enabled in %s' % ievent.channel)
734
fromtime = strtotime(ievent.rest)
736
ievent.reply("can't detect time")
738
result = logs.fromtimewithbot(ievent.channel, fromtime)
740
username = users.getname(ievent.userhost)
747
txt = i[4][nr:].strip()
748
res.append("[%s] <%s> %s" % (hourmin(float(i[1])), i[2], txt))
751
ievent.reply('no data found')
753
cmnds.add('log-time', handle_logtime, ['USER', 'WEB', 'ANON'])
754
examples.add('log-time', 'show log since given time', 'log-time 21:00')
756
def handle_active(bot, ievent):
757
""" active [<minback>] .. show who has been active, default is the
761
ievent.reply('log plugin is not enabled')
763
if ievent.channel not in logs.loglist:
764
ievent.reply('logging is not enabled in %s' % ievent.channel)
767
channel = ievent.args[0].lower()
769
channel = ievent.channel
772
for i in logs.fromtime(ievent.channel, time.time() - 15*60):
773
if i[2] not in result:
776
ievent.reply("active in the last %s minutes: " % minback, result)
777
elif len(result) == 1:
778
ievent.reply("%s is active" % result[0])
780
ievent.reply("nobody active")
782
cmnds.add('active', handle_active, ['USER', 'WEB', 'ANON'])
783
examples.add('active', 'active [<minutesback>] .. show who has been active \
784
in the last 15 or <minutesback> minutes', '1) active 2) active 600')
785
aliases.data['a'] = 'active'
787
def handle_line(bot, ievent):
788
""" line .. show activity of last hour """
790
ievent.reply('log plugin is not enabled')
792
if ievent.channel not in logs.loglist:
793
ievent.reply('logging is not enabled in %s' % ievent.channel)
797
for i in logs.fromtime(ievent.channel, time.time() - 60*60):
799
diff = float(now - float(i[1]))
802
times[int(diff/60)] += 1
806
result += "%s " % times[j]
809
result += '(number of lines per minute for the last hour .. \
810
most recent minute first)'
813
cmnds.add('line', handle_line, ['USER', 'WEB', 'ANON'])
814
examples.add('line', 'show activity for the last hour', 'line')
815
aliases.data['l'] = 'line'
817
# thnx to timp for this one
818
def handle_dayline(bot, ievent):
819
""" dayline .. show activity for last 24 hours """
821
ievent.reply('log plugin is not enabled')
823
if ievent.channel not in logs.loglist:
824
ievent.reply('logging is not enabled in %s' % ievent.channel)
828
for i in logs.fromtime(ievent.channel, time.time() - 24*60*60):
829
diff = int(now/1440 - float(i[1])/1440)
835
result += "%s " % times[j]
838
result += ' (nr lines per hour for the last day .. most recent hour first)'
841
cmnds.add('dayline', handle_dayline, ['USER', 'WEB', 'ANON'])
842
examples.add('dayline', 'show nr of lines spoken in last day', 'dl')
843
aliases.data['dl'] = 'dayline'
845
def handle_mono(bot, ievent):
846
""" mono [<nick>] .. show length of monologue """
848
ievent.reply('log plugin is not enabled')
850
if ievent.channel not in logs.loglist:
851
ievent.reply('logging is not enabled in %s' % ievent.channel)
856
nick = ievent.args[0].lower()
858
nick = ievent.nick.lower()
859
for i in logs.fromtime(ievent.channel, time.time() - 60*60):
860
if i[4].startswith('CMND:'):
862
if i[2].lower() == nick:
867
ievent.reply('%s lines of monologue' % teller)
869
ievent.reply("%s is not making a monologue" % nick)
871
cmnds.add('mono', handle_mono, ['USER', 'ANON'])
872
examples.add('mono', 'mono [<nick>] .. show nr lines of monologue', \
873
'1) mono 2) mono dunker')