#!/usr/pkg/bin/python """bot (robot) script to join an IRC channel and log it reconnects whenever necessary, rotates logs at predefined intervals""" Copyright = """ logbot -- log an IRC channel Copyright (C) 2005 John Comeau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ errormessage = "Not all needed libraries found, upgrade or check path" try: True # not defined in older Python releases except: True, False = 1, 0 try: import sys, os, types, re, pwd sys.path.append(os.path.join(pwd.getpwuid(os.geteuid())[5], 'lib', 'python')) from com.jcomeau import gpl, jclicense except: try: sys.stderr.write("%s\n" % errormessage) except: print errormessage raise # get name this program was called as self = os.path.split(sys.argv[0])[1] command = os.path.splitext(self)[0] # chop any suffix (extension) # now get name we gave it when we wrote it originalself = re.compile('[0-9A-Za-z]+').search(Copyright).group() # globals and routines that should be in every program # (yes, you could import them, but there are problems in that approach too) def DebugPrint(*whatever): return False # defined instead by pytest module, use that for debugging # other globals, specific to this program IRCPORT = 6667 # should probably allow it to be specified in command line PINGTIME = 30 # someday i'll have to check RFC DATEFORMAT = '%Y%m%d' timer, clock = None, None # set by start_timer() ircserver = None # set by start_client() import socket from select import select import time logdate = 'YYYYMMDD' def usage(*args): errorlevel = int(args[0]) output = sys.stderr if errorlevel == 0: output = sys.stdout output.write( 'Usage: %s SERVER CHANNEL LOG_DIRECTORY [DAILY | WEEKLY]\n' % command) sys.exit(errorlevel) def logbot(*args): """start up the bot""" DebugPrint('self, command, originalself', self, command, originalself) try: server, channel, directory = args[0], args[1], args[2] except: usage(1) try: rotate = args[3] except: rotate = 'weekly' if rotate not in ['daily', 'weekly']: usage(1) logrotate() start_timer(rotate) start_server(server, channel) while True: DebugPrint('waiting for communication from anywhere') rlist, wlist, xlist = select([ircserver, timer], [], [ircserver]) DebugPrint('got something', rlist, wlist, xlist) if ircserver in rlist: DebugPrint('ircserver sent something, trying to fetch it') try: received = ircserver.recv(1024) DebugPrint('received from ircserver:', received) if not len(received): DebugPrint('blank? something went wrong with ircserver, reconnecting') start_server(server, channel) else: for line in filter(len, re.compile('[\r\n]+').split(received)): DebugPrint('processing', line) message = filter(len, re.compile('[\s]+').split(line)) DebugPrint('message is', message) if message[1] == '451': DebugPrint('logging in to %s' % channel) login(channel) if message[0] == 'PING' and len(message) > 1: DebugPrint('sending PONG %s' % message[1]) try: ircserver.sendall('PONG %s' % message[1]) except: ircserver.send('PONG %s' % message[1]) if '!' in message[0]: username = filter(len, re.compile('[:!]').split(message[0])) try: nick, logname = username[0:2] except: nick, logname = '(unknown)', '(unknown)@(unknown.location)' if message[1] == 'PRIVMSG' and message[2] == channel: if len(message) > 4 and message[3] == ':\1ACTION': message[-1] = message[-1][0:len(message[-1]) - 1] logger(channel, directory, now() + ' * ' + nick + ' ' + join(' ', message[4:])) else: message[3] = message[3][1:] logger(channel, directory, now() + ' <%s>' % nick + ' ' + join(' ', message[3:])) elif message[1] == 'JOIN': logger(channel, directory, now() + ' *** %s (%s) has joined channel %s' % (nick, logname, channel)) elif message[1] == 'PART' or message[1] == 'QUIT': logger(channel, directory, now() + ' *** %s (%s) has left channel %s' % (nick, logname, channel)) else: DebugPrint('not logging message "%s"' % line) except: pass if timer in rlist: DebugPrint('timer sent something, trying to fetch it') try: received = timer.recv(1024) DebugPrint('timer says: %s' % received) if received == 'ping': DebugPrint('pinging server') ircserver.send('PING :%s\r\n' % int(time.time())) elif received == 'rotate': logrotate() received = 'timelog' if received == 'timelog': logger(channel, directory, time.asctime(time.localtime(time.time()))) except: pass if ircserver in xlist: DebugPrint('something went wrong with ircserver, reconnecting') start_server(server, channel) if False and DebugPrint('sleeping a sec'): time.sleep(1) def now(*args): return time.strftime('%H:%M:%S', time.localtime(time.time())) def join(*args): "for pythons without str.join" string, array = args if hasattr(str, 'join'): return string.join(array) else: joined = '' for index in range(0, len(array)): joined = joined + array[index] if index != (len(array) - 1): joined = joined + string return joined def start_server(*args): global ircserver server, channel = args[0:2] if ircserver: ircserver.close() ircserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ircserver.bind(('0.0.0.0', 0)) DebugPrint('connecting to %s' % server) while True: try: ircserver.connect((server, IRCPORT)) break except: DebugPrint('connect failed, waiting...') time.sleep(10) DebugPrint('connected to %s' % server) login(channel) def logrotate(*args): global logdate logdate = time.strftime(DATEFORMAT, time.localtime(time.time())) def logger(*args): cleanchannel = args[0][1:] directory = args[1] line = args[2] logfile = os.path.join(directory, '%s_%s.log' % (cleanchannel, logdate)) channel_log = open(logfile, 'a') DebugPrint('logging "%s" to %s' % (line, logfile)) channel_log.write('%s\n' % line) channel_log.close() symlink = os.path.join(directory, cleanchannel + '.log') try: realfile = os.readlink(symlink) except: realfile = '' if realfile != logfile: if os.path.exists(symlink): os.unlink(symlink) os.symlink(logfile, symlink) def login(channel): try: ircserver.sendall('USER nobody . . : Channel Logger\r\n') ircserver.sendall('NICK logger\r\n') ircserver.sendall('JOIN %s\r\n' % channel) except: ircserver.send('USER nobody . . : Channel Logger\r\n') ircserver.send('NICK logger\r\n') ircserver.send('JOIN %s\r\n' % channel) DebugPrint('logged in to %s' % channel) def start_timer(*args): global timer, clock rotate = args[0] try: timer, clock = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) except: timer = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) clock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) timer.bind(('0.0.0.0', 0)) clock.connect(timer.getsockname()) pid = os.fork() if pid: # parent process clock.close() return else: timer.close() offset = time.clock() % 1 if offset: DebugPrint('waiting %s seconds for seconds to go to zero' % offset) time.sleep(offset) clock.send('timelog') while True: hour, min, sec, weekday = time.localtime(time.time())[3:7] midnight = (hour + min + sec == 0) if midnight: if rotate == 'daily': clock.send('rotate') elif not weekday: clock.send('rotate') clock.send('timelog') if not sec % PINGTIME: DebugPrint('timer sending ping notification') clock.send('ping') time.sleep(1) if False and DebugPrint('timer sending tick'): clock.send('tick') if __name__ == '__main__': # if this program was imported by another, the above test will fail, # and this following code won't be used... function = command; args = sys.argv[1:] # default case if command == originalself: try: if len(args) and eval('type(%s) == types.FunctionType' % args[0]): function = sys.argv[1]; args = sys.argv[2:] except: pass eval('%s%s' % (function, repr(tuple(args)))) else: # if you want something to be done on import, do it here; otherwise pass pass