Tue, 18 Aug 2015 14:38:54 +0300
Use python3 in the shebang
''' Copyright 2014-2015 Teemu Piippo All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' from __future__ import print_function import os import re import time import sys from configfile import Config Modules = {} Commands = {} Hooks = {} class CommandError (Exception): def __init__ (self, value): self.value = value def __str__ (self): return self.value # # init_data() # # Initializes command module data # def init_data(): global Commands global Modules files = os.listdir ('.') numHooks = 0 for fn in files: if fn[0:4] != 'mod_' or fn[-3:] != '.py': continue fn = fn[0:-3] globals = {} module = __import__ (fn) Modules[fn] = module if not hasattr (module, 'ModuleData'): continue # No manifest if 'commands' in module.ModuleData: for cmd in module.ModuleData['commands']: install_command (cmd, module) if 'hooks' in module.ModuleData: for key, hooks in module.ModuleData['hooks'].items(): for hook in hooks: if key not in Hooks: Hooks[key] = [] Hooks[key].append ({'name': hook, 'func': getattr (module, hook)}) numHooks += 1 print ('''Loaded module %s''' % fn) print ('''Loaded %d commands and %d hooks in %d modules''' % (len (Commands), numHooks, len (Modules))) def install_command (cmd, module): if cmd['args'] == None: cmd['args'] = '' Modules[module.__name__] = module cmd['module'] = module cmd['regex'] = make_regex (cmd['args']) cmd['argnames'] = [] for argname in cmd['args'].split (' '): argname = argname[1:-1] if argname[-3:] == '...': argname = argname[0:-3] if argname == '': continue cmd['argnames'].append (argname) Commands[cmd['name']] = cmd # # command_error (message) # # Raises a command error # def command_error (message): raise CommandError (message) # # is_available (cmd, ident, host) # # Is the given command available to the given user? # def is_available (cmd, ident, host): if cmd['level'] == 'admin' \ and not "%s@%s" % (ident, host) in Config.get_value ('admins', default=[]): return False return True # # response_function # g_responsePages = [[]] g_responsePageNum = 0 g_lastConfirm = 0 g_confirmCommand = None class Confirmxeption (Exception): def __init__ (self, id, value): self.id = id self.message = value def __str__ (self): return self.message def response_function (message): global g_responsePages if len (g_responsePages[-1]) > 4: g_responsePages.append ([message]) else: g_responsePages[-1].append (message) def confirm_function (id, message): raise Confirmxeption (id, message) def print_responses (commandObject): global g_responsePages global g_responsePageNum bot = commandObject['bot'] replyto = commandObject['replyto'] # Check bounds if g_responsePageNum >= len (g_responsePages): bot.privmsg (replyto, "No more messages.") return # Print this page for line in g_responsePages[g_responsePageNum]: bot.privmsg (replyto, line) # Advance page cursor g_responsePageNum += 1 # If this was not the last page, tell the user there's more if g_responsePageNum != len (g_responsePages): num = (len (g_responsePages) - g_responsePageNum) bot.privmsg (replyto, "%d more page%s, use .more to continue output" \ % (num, 's' if num != 1 else '')) # # check_same_caller (comm1, comm2) # # Are the two commands called by the same person? # def check_same_caller (comm1, comm2): return comm1['bot'].name == comm2['bot'].name \ and comm1['sender'] == comm2['sender'] \ and comm1['ident'] == comm2['ident'] \ and comm1['host'] == comm2['host'] def exec_command (commandObject): global g_lastConfirm global g_confirmCommand if 'function' in commandObject['info']: func = commandObject['info']['function'] else: cmdname = commandObject['cmdname'] try: func = getattr (commandObject['module'], 'cmd_' + cmdname) except AttributeError: command_error ('''command '%s' does not have a definition''' % cmdname) try: func (**commandObject) except Confirmxeption as e: if time.time() - g_lastConfirm < 15 and g_confirmCommand != None: command_error ('''another confirm is underway''') g_lastConfirm = time.time() response_function (str (e) + ' (.yes/.no)') commandObject['confirmed'] = e.id g_confirmCommand = commandObject except Exception as e: command_error (str (e)) # # call_command (bot, message, cmdname, **kvargs) # # Calls a cobalt command # def call_command (bot, message, cmdname, **kvargs): global g_responsePages global g_responsePageNum try: cmd = Commands[cmdname] except KeyError: print ('''No such command %s''' % cmdname) return if not is_available (cmd=cmd, ident=kvargs['ident'], host=kvargs['host']): command_error ('''you may not use %s''' % cmdname) match = re.compile (cmd['regex']).match (message) if match == None: # didn't match command_error ('''usage: %s %s''' % (cmd['name'], cmd['args'])) # .more is special as it is an interface to the page system. # Anything else resets it. if cmdname != 'more': g_responsePages = [[]] g_responsePageNum = 0 i = 1 args = {} for argname in cmd['argnames']: args[argname] = match.group (i) i += 1 print ('''%s was called by %s''' % (cmdname, kvargs['sender'])) commandObject = kvargs commandObject['bot'] = bot commandObject['cmdname'] = cmdname commandObject['args'] = args commandObject['reply'] = response_function commandObject['confirm'] = confirm_function commandObject['confirmed'] = 0 commandObject['commandObject'] = commandObject commandObject['info'] = cmd commandObject['module'] = cmd['module'] commandObject['error'] = command_error exec_command (commandObject) # Print the first page of responses. if cmdname != 'more': print_responses (commandObject) def call_hook (bot, hookname, **kvargs): global g_responsePages global g_responsePageNum hookObject = kvargs hookObject['bot'] = bot g_responsePages = [[]] g_responsePageNum = 0 if 'replyto' in hookObject: hookObject['reply'] = response_function if hookname in Hooks: for hook in Hooks[hookname]: hook['func'] (**hookObject) print_responses (hookObject) def confirm (cmd, yes): global g_confirmCommand if g_confirmCommand == None: cmd['reply'] ('%s to what?' % cmd['cmdname']) return if not check_same_caller (cmd, g_confirmCommand): return if yes: exec_command (g_confirmCommand) else: cmd['reply'] ('okay then') g_confirmCommand = None # # get_available_commands # # Gets a list of commands available to the given user # def get_available_commands (ident, host): result=[] for cmdname,cmd in Commands.items(): if not is_available (cmd, ident, host): continue result.append (cmdname) return result # # get_command_by_name # # Gets a command by name # def get_command_by_name (name): try: return Commands[name] except: return None # # make_regex # # Takes the argument list and returns a corresponding regular expression # def make_regex (arglist): if arglist == None: return '^.+$' gotoptional = False gotvariadic = False regex = '' for arg in arglist.split (' '): if gotvariadic: raise CommandError ('''variadic argument is not last''') if arg == '': continue gotliteral = False if arg[0] == '[' and arg[-1] == ']': arg = arg[1:-1] gotoptional = True elif arg[0] == '<' and arg[-1] == '>': if gotoptional: raise CommandError ('''mandatory argument after optional one''') arg = arg[1:-1] else: gotliteral = True if arg[-3:] == '...': gotvariadic = True arg = arg[:-3] if gotoptional == False: regex += r'\s+' else: regex += r'\s*' if gotliteral: regex += arg elif gotoptional: if gotvariadic: regex += r'(.*)' else: regex += r'([^ ]*)' else: if gotvariadic: regex += r'(.+)' else: regex += r'([^ ]+)' if not gotvariadic: regex += r'\s*' return '^[^ ]+%s$' % regex def irc_command (**command): def result (fn): command['name'] = fn.__name__ command['description'] = fn.__doc__ command['function'] = fn if command['description'] == None: command['description'] = '' if 'level' not in command: command['level'] = 'normal' if 'args' not in command: command['args'] = None install_command (command, sys.modules[fn.__module__]) return fn return result