#!/usr/bin/env python # written by jmoiron, jmoiron.net # licensed under the GNU GPL v2 # a copy of the license can be found: # http://www.gnu.org/licenses/gpl.txt """ Keeps a log of DCC transfer events. This is not meant for use with a bot, so it only keeps track of DCC RECV events. """ import xchat import sys, os import ConfigParser import datetime #import re from exceptions import ValueError __module_name__ = "dcclog" __module_version__ = "0.1" __module_description__ = "dcc log" # to enable some debugging output, set to True __debugging__ = False if __debugging__: import traceback # we're going to latch onto the context we're loaded from CONTEXT = xchat.get_context() def print_debug(string): global __debugging__ if __debugging__: string = str(string) CONTEXT.prnt("\00302" + string + "\003") def print_error(string): string = str(string) CONTEXT.prnt("\00304" + string + "\003") def print_help(string): string = str(string) CONTEXT.prnt("\00302" + string + "\003") def print_success(string): string = str(string) CONTEXT.prnt("\00303" + string + "\003") def echo(string): string = str(string) CONTEXT.prnt("\00300" + string + "\003") print_info = print_success XCHAT_DIR = xchat.get_info("xchatdir") if not XCHAT_DIR.endswith("/"): XCHAT_DIR += "/" XCHAT_DIRLIST = os.listdir(XCHAT_DIR) # logfile format: # $D = Date (2006.10.25) # $T = Time (24hr) # $t = Time (12hr) # $N = Nick of sender # $F = filename # $C = CPS # $R = bytes received (pos) # $Z = filesize # $U = resume # %S = status CONFIG_FILE = XCHAT_DIR + "jonaspy.conf" CONFIG_FORMAT_OPTIONS = ['D', 'T', 't', 'N', 'F', 'C', 'R', 'Z', 'U', 'S'] CONFIG_DEFAULT = { "logformat" : "$D $T :$S: $N \"$F\" [$R/$Z @ $C from byte $U]", "logfile" : XCHAT_DIR + "dcclog.txt", } DCC_HOOKS = {} DCC_RECV_EVENTS = [ "DCC RECV Abort", "DCC RECV Complete", "DCC RECV Failed", "DCC Stall", "DCC Timeout", ] cfg = ConfigParser.SafeConfigParser() # cfg stuff throws lots of exceptions def set_cfg_defaults(): global cfg, CONFIG_DEFAULT try: cfg.add_section('dcclog') for name, value in CONFIG_DEFAULT.items(): cfg.set('dcclog', name, value) except: if __debugging__: traceback.print_exc(sys.stdout) def save_cfg(filename): global cfg cfgFile = open(filename, 'w') cfg.write(cfgFile) cfgFile.close() # we try to read the config file try: cfgFile = open(CONFIG_FILE) cfg.readfp(cfgFile) cfgFile.close() # if the config doesn't have 'dcclog' if not cfg.has_section('dcclog'): set_cfg_defaults() save_cfg(CONFIG_FILE) # if it doesn't exist, we make one from defaults except: set_cfg_defaults() save_cfg(CONFIG_FILE) # TODO: we should make sure our configuration options actually exist # inside the configuration file, and write the defaults if they dont # right now we're just testing for section existence # by now the cfg has the right configs cmd_help = {} gen_help = [ "\002commands:\002\n", " \037no options\037 - shows configuration options\n", " \037[help, ?]\037 - prints this help or detailed help for 'cmd'\n", " \037set\037 [opt] [value] - sets configuration option 'opt' to 'value'\n", "\002extra help options:\002\n", " \037logformat\037 - detailed help on'logformat' option\n", ] cmd_help['msg'] = " " + " ".join(gen_help) cmd_help['help'] = """\n\002/dcclog\002 \037[help, ?]\037 \n \00303If is provided, it prints detailed help on the command 'cmd'. If not, it prints the general help for dcclog.\00303""" cmd_help['set'] = """\n\002/dcclog\002 \037[set]\037 [opt] [value]\n \00303Sets the configuration setting "opt" to "value". Take care not to set it to options that will break the logger, such as a logformat string that is invalid or a logfile in a location you do not write access to.\00303""" cmd_help['logformat'] = """\n\002logformat special characters\003\n \00303The log format can be changed to whatever you want, on the fly. A number of special interpolation sequences are available and begin with '$' followed by a special letter. The character '$' is not illegal outside of these sequences, but it will be stripped and using it is NOT recommended. No escaping has been implemented at this time. The sequences are:\00303 $D = Date (2006.10.25) $T = Time (24hr) $t = Time (12hr) $N = Nick of sender $F = filename $C = CPS $R = bytes received $Z = filesize $U = resume point (0 if not resumed) $S = dcc status """ class Entry: def __init__(self, dcc_obj): # datetime.datetime(2006, 10, 14, 16, 44, 52, 660814) self.dcc_obj = dcc_obj self.datetime = datetime.datetime.now() self.date_ts = self.datetime.strftime('%Y.%m.%d') self.time_24 = self.datetime.strftime('%H:%M:%S') self.time_12 = self.datetime.strftime('%I:%M:%S %P') self.nick = str(dcc_obj.nick) self.filename = str(dcc_obj.file) self.cps = str(dcc_obj.cps/1024) + 'KB/sec' self.received = "%.2F" % (dcc_obj.pos/1048576.0) + 'MB' self.filesize = "%.2F" % (dcc_obj.size/1048576.0) + 'MB' self.resume = str(dcc_obj.resume) self.status = ['QUEUED', 'ACTIVE', 'FAILED ', 'COMPLETE', 'CONNECTED', 'ABORTED '][int(dcc_obj.status)] self.format_options = { 'D' : self.date_ts, 'T' : self.time_24, 't' : self.time_12, 'N' : self.nick, 'F' : self.filename, 'C' : self.cps, 'R' : self.received, 'Z' : self.filesize, 'U' : self.resume, 'S' : self.status, } # return a string according to our log format def _createLine(self): global cfg, CONFIG_FORMAT_OPTIONS global print_debug format = cfg.get('dcclog', 'logformat').split('$') s = '' print_debug(str(format)) for piece in format: if not piece: continue if self.format_options.has_key(piece[0]): s += self.format_options[piece[0]] + piece[1:] return s def commit(self, filename): global print_debug line = self._createLine() print_debug(line) f = open(filename, 'a') f.write(line + '\n') f.close() # xchat.nickcmp works in a c-like, bad fashion def sameNick(n1, n2): if not xchat.nickcmp(n1, n2): return True return False def getDccObject(botname, filename): l = xchat.get_list('dcc') for item in l: if sameNick(item.nick, botname) and item.file == filename: return item def dccRecvComplete(split, full): global cfg # split: filename, filepath, botname, CPS dcc_obj = getDccObject(split[2], split[0]) print_debug(dcc_obj) entry = Entry(dcc_obj) entry.commit(cfg.get('dcclog', 'logfile')) def dccRecvAbort(split, full): global cfg # split: botname, filename dcc_obj = getDccObject(split[0], split[1]) entry = Entry(dcc_obj) entry.commit(cfg.get('dcclog', 'logfile')) dccRecvFailed = dccRecvComplete # split: filename, filepath, botname, error def dccStall(split, full): # split: dcctype, filename, botname global cfg dcc_obj = getDccObject(split[2], split[1]) print_debug(dcc_obj) entry = Entry(dcc_obj) entry.commit(cfg.get('dcclog', 'logfile')) dccTimeout = dccStall def dispatch(split, full, event): print_debug('Event captured: \"%s\"' % (event)) try: { "DCC RECV Complete" : dccRecvComplete, "DCC RECV Abort" : dccRecvAbort, "DCC RECV Failed" : dccRecvFailed, "DCC Stall" : dccStall, "DCC Timeout" : dccTimeout, }[event](split, full) except: if __debugging__: traceback.print_exc(sys.stdout) def showHelp(split, full): # /dcclog help [cmd] global cmd_help if len(split) == 3: if cmd_help.has_key(split[2]): print_help(cmd_help[split[2]]) return print cmd_help['msg'] # TODO: future -- have setting 'logformat' print out a sample def setOption(split, full): # /dcclog set