Example #1
0
 def __init__(self):
     self._escape_parser=EscapeParser()
     self.macros = {}
     self.log=open(r'c:\temp\log_process.log','w',0)
     self.log.write('Init MudProcessor\n')
     self.buffering = False
     self.buffer = ''
     self.missed_heartbeats = 0
     self.heartbeat_lc = None
     self.settings_directory=''
     self.server_echo = True
     self.triggers = []
     self.aliases = []
     self.gmcp_events = []
     self.gmcp={}
     self.last_command_sent = ''
     self.root=self
     self.tracing = False
     self.active_channels = ['main']
     self.state={}
     self.event_handlers={}
     self.reactor = None
     self.block=[]
     self.queue_to_send=[]
     self.connected=False
Example #2
0
    def __init__(self, factory):
        self.factory = factory
        self.root=self
        self.telnet = None
        self.triggers = []
        self.aliases = []
        self.baked_in_macros = gui_macros.copy()
        self.macros = self.baked_in_macros.copy()
        self.modules_loaded = set()
        self._escape_parser = EscapeParser()
        self.tracing = False
        self.server_echo = False
        self.console_ns = {'realm': self}
        self.console = InteractiveConsole(self.console_ns)
        self._last_line_end = None
        self.wrapper = TextWrapper(width = 100, 
                                   drop_whitespace = False)

        self.protocols = []
        self._closing_down = False
        self.gmcp_handler = None
        self.gmcp_events=[]
        self.gmcp={}
        self.state={}
        self.module_settings_dir=''
        
        self.active_channels=['main']
Example #3
0
class MudProcessor(LineReceiver):
    delimiter = "\n"
    MAX_LENGTH = 131072 * 8 * 100

    def __init__(self):
        self._escape_parser=EscapeParser()
        self.macros = {}
        self.log=open(r'c:\temp\log_process.log','w',0)
        self.log.write('Init MudProcessor\n')
        self.buffering = False
        self.buffer = ''
        self.missed_heartbeats = 0
        self.heartbeat_lc = None
        self.settings_directory=''
        self.server_echo = True
        self.triggers = []
        self.aliases = []
        self.gmcp_events = []
        self.gmcp={}
        self.last_command_sent = ''
        self.root=self
        self.tracing = False
        self.active_channels = ['main']
        self.state={}
        self.event_handlers={}
        self.reactor = None
        self.block=[]
        self.queue_to_send=[]
        self.connected=False
        
        
    def heartbeat(self):
        self.missed_heartbeats+=1
        if self.missed_heartbeats == 3:
            self.stop()
        else:
            self.send_to_client('ping', [])
        
    def stop(self):
        self.send_to_client('bye', [])
        self.log.close()
        self.transport.loseConnection()
        from twisted.internet import reactor
        reactor.stop()
        
    def connectionMade(self):
        self.log.write('ConnectionMade\n')
        self.connected = True
        self.transport.write(json.dumps(["hello", [self.macros]]) + "\n")
        self.heartbeat_lc = LoopingCall(self.heartbeat)
        self.heartbeat_lc.start(10)
        
        for meth,params in self.queue_to_send:
            self.send_to_client(meth, params)
    
    def send_to_client(self, meth, params):
        #print('send to processor: %s, %s'%(meth, json.dumps(params)))
        if self.connected:
            line = json.dumps([meth, params])
            if len(line)>1000:
                self.transport.write("%buff_begin%\n")
                while len(line)>1000:
                    self.transport.write(line[:1000]+"\n")
                    line=line[1000:]
                self.transport.write(line+"\n")
                self.transport.write("%buff_end%\n")
            else:
                self.transport.write(json.dumps([meth, params]) + "\n")
        else:
            self.queue_to_send.append((meth,params))

    
    def lineReceived(self, line):
        self.log.write('Line: %s \n'%line)
        if line == '%buff_begin%':
            self.buffering = True
            self.buffer = ''
        elif line == '%buff_end%':
            if self.buffering == True:
                self.lineReceived_recomposed(self.buffer)
            self.buffering = False
        else:
            if self.buffering == True:
                self.buffer += line
            else:
                self.lineReceived_recomposed(line)
    def lineReceived_recomposed(self, line):
        
            
        meth,rest = json.loads(line)
        if meth == "close":
            self.stop()
        elif meth == "ping":
            self.missed_heartbeats=-1
            
        elif meth == "hello":
            self.log.write('Hello received\n')
            name = rest[0]
            self.name = name
            
        elif meth == 'mud_line':
            metaline = json_to_metaline(rest[0])
            display_line = bool(rest[1])
            #self.match_triggers(metaline, display_line)
            #self.transport.write(json.dumps(['display_line', [metaline_to_json(metaline), 1]])+'\n')
            self.send_to_client('display_line', [metaline_to_json(metaline),1])
        elif meth == 'user_line':
            line = rest[0]
            self.log.write('User Line: %s\n'%line)
            #self.transport.write(json.dumps(['send_to_mud',line])+'\n')
            self.send_to_client('send_to_mud', line)
        elif meth == 'do_block':
            lines = json.loads(rest[0])
            block = []
            for l in lines:
                block.append(json_to_metaline(l))
            self.blockReceived(block)
        
        elif meth == 'do_triggers':
            metaline=json_to_metaline(rest[0])
            display_line = bool(rest[1])
            self.metalineReceived(metaline, display_line)
        
        elif meth == 'do_aliases':
            line = rest[0]
            self.server_echo = bool(rest[1])
            echo = bool(rest[2])
            self.parseSend(line, echo)
        elif meth == 'do_gmcp':
            pair = rest[0]
            gmcp_key,gmcp_data = pair
            self.gmcp[gmcp_key]=gmcp_data
            for gmcp_event in self.gmcp_events:
                gmcp_event(pair, self)
        else:
            raise ValueError("bad line: %s" % line)
        #self.transport.write(json.dumps(["ack", [meth,rest]]) + "\n")
        self.send_to_client('ack', [meth,rest])
    
    
    
    def load_module(self, cls, _sort = True):
        """Load the triggers, aliases, macros and other modules of the given
        module.
        """
        robmod = cls(self)
        for mod in robmod.modules:
            self.load_module(mod, _sort = False)
        if _sort:
            self.triggers.sort(key = attrgetter("sequence"))
            self.gmcp_events.sort(key = attrgetter("sequence"))
            self.aliases.sort(key = attrgetter("sequence"))
        return robmod
    
    def registerEventHandler(self, eventName, eventHandler):
        if not eventName in self.event_handlers:
            self.event_handlers[eventName]=[]
        self.event_handlers[eventName].append(eventHandler)
    
    
    def fireEvent(self, eventName, *args):
        self.send_to_client('event', [eventName,args])
        self.fireEventLocal(eventName, *args)
    
    def fireEventLocal(self, eventName, *args):
        if eventName in self.event_handlers:
            for eh in self.event_handlers[eventName]:
                self.reactor.callLater(0, eh, *args)   
                
    def get_state(self, item):
        if item in self.state:
            return self.state[item]
        else:
            return ''
        
    def set_state(self, name, value):
        self.state[name]=value
        self.send_to_client('set_state', [name,value])
        
    def metalineReceived(self, metaline, display_line):
        realm = TriggerMatchingRealm(metaline, parent = self, root = self,
                                     display_line = display_line)
        try:
            realm.process()
        except:
            self.handle_exception(traceback.format_exc())
    
    def cwrite(self, line, display_line=True, channels=['main']):
        ml=taggedml(line)
        ml.channels = channels
        self.write(ml, display_line)
        
    def write(self, line, display_line = True):
        if not isinstance(line, (basestring, Metaline)):
            line = str(line)
        if isinstance(line, basestring):
            metaline = simpleml(line, fg_code(WHITE, False), bg_code(BLACK))
            metaline.wrap = False
        else:
            metaline = line
        self.send_to_client('display_line', [metaline_to_json(metaline),int(display_line)])
    
    def send_mud(self, line):
        self.last_command_sent = line
        self.send_to_client('send_to_mud', line)
        
    def send(self, line, echo = True):
        """Match aliases against the line and perhaps send it to the MUD."""
        echo = not self.server_echo and echo
        realm = AliasMatchingRealm(line, echo, parent = self, root = self)
        try:       
            realm.process()
        except:
            self.handle_exception(traceback.format_exc())
        
    def parseSend(self, string, echo):
        for line in list(self._escape_parser.parse(string + '\n')):
            self.send(line, echo)
            
    def setActiveChannels(self, channels):
        self.active_channels = channels
        self.send_to_client('set_active_channels', channels)
        
    def handle_exception(self, trace):
        self.send_to_client('error', trace)
        
        
    def blockReceived(self, block):
        self.block = block 
        realm = TriggerBlockMatchingRealm(self.block, parent = self, root = self, 
                                          display_group=True)
        realm.process()
    
    def set_timer(self, time, fn, *args, **kwargs):
        from twisted.internet import reactor
        realm = TimerRealm(self)
        timer = self.reactor.callLater(time, fn, realm, *args, **kwargs)
        return timer
    
    def debug(self, msg):
        self.send_to_client('debug', [msg])
        
    def trace(self, line):
        """Write the argument to the screen if we are tracing, elsewise do
        nothing.
        """
        if self.tracing:
            self.write("TRACE: " + line)

    def trace_thunk(self, thunk):
        """If we're tracing, call the thunk and write its result to the
        outputs. If not, do nothing.
        """
        if self.tracing:
            self.write("TRACE: " + thunk())
Example #4
0
class RootRealm(object):
    """The root of the realms hierarchy. This is what macros and top-level
    modules deal with.
    """

    def __init__(self, factory):
        self.factory = factory
        self.root=self
        self.telnet = None
        self.triggers = []
        self.aliases = []
        self.baked_in_macros = gui_macros.copy()
        self.macros = self.baked_in_macros.copy()
        self.modules_loaded = set()
        self._escape_parser = EscapeParser()
        self.tracing = False
        self.server_echo = False
        self.console_ns = {'realm': self}
        self.console = InteractiveConsole(self.console_ns)
        self._last_line_end = None
        self.wrapper = TextWrapper(width = 100, 
                                   drop_whitespace = False)

        self.protocols = []
        self._closing_down = False
        self.gmcp_handler = None
        self.gmcp_events=[]
        self.block_handlers=[]
        self.gmcp={}
        self.state={}
        self.module_settings_dir=''
        
        self.active_channels=['main']
        self.block=[]
        self.hide_lines=0
        self.last_line=None
        
        self.gui=None
        
        self.accessibility_mode = False
        self.event_handlers={}
        self.safe_to_send=True
    #Bidirectional, or just ambivalent, functions.
    
    def registerEventHandler(self, eventName, eventHandler):
        if not eventName in self.event_handlers:
            self.event_handlers[eventName]=[]
        self.event_handlers[eventName].append(eventHandler)
    
    
    def fireEvent(self, eventName, *args):
        if eventName in self.event_handlers:
            for eh in self.event_handlers[eventName]:
                self.factory.reactor.callLater(0, eh, *args)
                
    def get_state(self, item):
        if item in self.state:
            return self.state[item]
        else:
            return ''
        
    def set_state(self, item, value):
        self.state[item]=value
        
    def hide_next_lines(self,num_lines):
        self.hide_lines+=num_lines
        
    def clear_modules(self):
        """Restore our state to a pristine (ie, blank) condition.
        """
        #keep in place so references to these still work
        self.triggers[:] = []
        self.aliases[:] = []
        self.gmcp_events[:]=[]
        self.macros.clear()
        self.macros.update(self.baked_in_macros)
        self.modules_loaded = set()

    def reload_main_module(self):
        """Clear ourselves into a pristine state and load the main module
        again.
        """
        self.clear_modules()
        cls = load_file(self.factory.main_module_name)
        self.load_module(cls)

    def load_module(self, cls, _sort = True):
        """Load the triggers, aliases, macros and other modules of the given
        module.
        """
        if cls in self.modules_loaded:
            return
        #add now, so that we can avoid circular dependencies
        self.modules_loaded.add(cls)
        try:
            robmod = cls(self)
            for mod in robmod.modules:
                self.load_module(mod, _sort = False)
            if _sort:
                self.triggers.sort(key = attrgetter("sequence"))
                self.aliases.sort(key = attrgetter("sequence"))
                self.gmcp_events.sort(key = attrgetter("sequence"))
        except:
            self.modules_loaded.remove(cls)
            raise
        return robmod

    def close(self):
        """Close up our connection and shut up shop.
        
        It is guaranteed that, on registered connection event receivers,
        connection_lost will be called before close.
        """
        if not self._closing_down:
            #lose the connection first.
            self.telnet.close()
        else:
            #connection's already lost, we don't need to wait
            for prot in self.protocols:
                prot.close()
        self._closing_down = True

    def addProtocol(self, protocol):
        self.protocols.append(protocol)

    def connectionLost(self):
        """The link to the MUD died.

        It is guaranteed that this will be called before close on connection
        event receivers.
        """
        message = time.strftime("Connection closed at %H:%M:%S.")
        colour = HexFGCode(0xFF, 0xAA, 0x00) #lovely orange
        metaline = simpleml(message, colour, bg_code(BLACK))
        self.write(metaline)
        for prot in self.protocols:
            prot.connectionLost()
        #we might be waiting on the connection to die before we send out
        #close events
        if self._closing_down:
            for prot in self.protocols:
                prot.close()
        self._closing_down = True

    def connectionMade(self):
        """The MUD's been connected to."""
        message = time.strftime("Connection opened at %H:%M:%S.")
        colour = HexFGCode(0xFF, 0xAA, 0x00) #lovely orange
        metaline = simpleml(message, colour, bg_code(BLACK))
        self.write(metaline)
        for prot in self.protocols:
            prot.connectionMade()

    def trace_on(self):
        """Turn tracing (verbose printing to the output screen) on."""
        if not self.tracing:
            self.tracing = True
            self.trace("Tracing enabled!")

    def trace_off(self):
        """Turn tracing off."""
        if self.tracing:
            self.trace("Tracing disabled!")
            self.tracing = False

    def maybe_do_macro(self, chord):
        """Try and run a macro against the given keychord.

        A return value of True means a macro was found and run, False means
        no macro was found, or a macro returned True (meaning allow the GUI
        to continue handling the keypress).
        """
        if chord in self.macros:
            macro = self.macros[chord]
            allow_gui_continue = False
            try:
                allow_gui_continue = macro(self)
            except Exception:
                traceback.print_exc()
            return not allow_gui_continue
        else:
            return False

    #Going towards the screen

    def gmcpReceived(self, gmcp_pair):
        """Take GMCP data and do something with it"""
        for gmcp_event in self.gmcp_events:
            gmcp_event(gmcp_pair, self)

    def blockReceived(self, block):
        if len(block) > 0:
            realm = TriggerBlockMatchingRealm(block, parent = self, root = self,    
                                          send_line_to_mud = self.telnet.sendLine)
            realm.process()
     
    def setActiveChannels(self, channels):
        self.active_channels = channels       
            
    def metalineReceived(self, metaline):
        """Match a line against the triggers and perhaps display it on screen.
        """
        realm = TriggerMatchingRealm(metaline, parent = self,  root = self,
                                     send_line_to_mud = self.telnet.sendLine)
        realm.process()

    def cwrite(self, line, soft_line_start=False):
        ml=taggedml(line)
        self.write(ml, soft_line_start)
        
    def write(self, line, soft_line_start = False):
        if self.hide_lines>0:
            self.hide_lines-=1
            return
        
        """Write a line to the screen.
        
        This forcibly converts its argument to a Metaline.
        """
        if not isinstance(line, (basestring, Metaline)):
            line = str(line)
        if isinstance(line, basestring):
            metaline = simpleml(line, fg_code(WHITE, False), bg_code(BLACK))
            metaline.wrap = False
            metaline.soft_line_start = soft_line_start
        else:
            metaline = line
        #we don't need to close off the ends of the note, because thanks to
        #the magic of the ColourCodeParser, each new line is started by the
        #implied colour, so notes can't bleed out into text (though the 
        #reverse can be true).

        #this needs to be before the futzing with NLs and GA, because textwrap
        #obliterates all other newlines.
        metaline = metaline.wrapped(self.wrapper)

        #we don't actually append newlines at the end, but the start. This
        #simplifies things, because we don't use a newline where a soft line
        #end meets a soft line start, so there's only one place in this code
        #that can add newlines.
        if self._last_line_end is not None:
            if self._last_line_end == 'hard' or not metaline.soft_line_start:
                metaline.insert(0, '\n')
                
        for prot in self.protocols:
            prot.metalineReceived(metaline,self.active_channels)

        self._last_line_end = metaline.line_end

    def trace(self, line):
        """Write the argument to the screen if we are tracing, elsewise do
        nothing.
        """
        if self.tracing:
            self.write("TRACE: " + line)

    def trace_thunk(self, thunk):
        """If we're tracing, call the thunk and write its result to the
        outputs. If not, do nothing.
        """
        if self.tracing:
            self.write("TRACE: " + thunk())

    #Going towards the MUD.

    def receive_gui_line(self, string):
        """Send lines input into the GUI to the MUD.
        
        NOTE: this may have the power to execute arbitrary Python code. Thus,
        triggers and aliases should avoid using this, as they may be 
        vulnerable to injection from outside sources. Use send instead.
        """
        if string.startswith('/'):
            self.console.push(string[1:])
        else:
            for line in self._escape_parser.parse(string + '\n'):
                self.send(line)

    def safe_send(self, line, echo = True):
        if self.safe_to_send:
            self.send(line, echo)
            
            
    def send(self, line, echo = True):
        """Match aliases against the line and perhaps send it to the MUD."""
        echo = not self.server_echo and (echo and not self.accessibility_mode)
        realm = AliasMatchingRealm(line, echo, parent = self, root = self,
                                   send_line_to_mud = self.telnet.sendLine)
        realm.process()
    
    def set_timer(self, seconds, f, realm):
        return self.factory.reactor.callLater(seconds, f, realm)
 def setUp(self):
     self.eparser = EscapeParser()
class TestEscapes(object):
 
    def setUp(self):
        self.eparser = EscapeParser()

    tests = [('foo\n', #basic.
             ['foo']),

             ('\n',
              ['']),   
             
             ('foo\\nbar\n', #multiple things on one input.
             ['foo', 'bar']),

             ('foo\\\nbar\n',
              ['foo\\', 'bar']),

             ('foo;bar\n', #semicolon linebreak this time
             ['foo', 'bar']),

             ('foo\\x0Abar\\012', #octal and hex escapes.
             ['foo', 'bar']),

             ('\\x57\n',
             ['\x57']),

             ('\\100\\10\\1\n',
             ['\100\10\1']),

             ('\\\\foo\n', #escaped backslashes.
             ['\\foo']),

             ('\\;bar\n', #escaped semicolons
             [';bar']),

             ('\\foo\n', #and unknown escapes.
             ['\\foo'])]
             
    error_tests = [('\\xoink\n', InvalidEscape),
                   ('bar\\', InvalidInput),
                   ('foo\\xb', InvalidEscape)]

    def test_normals(self):
        for test, expected in self.tests:
            yield self.do_one_normal, test, expected

    def do_one_normal(self, input, expected):
        res = list(self.eparser.parse(input))
        assert res == expected, res

    def run_error_tests(self):
        for test, err in self.error_tests:
        	yield self.do_one_error, test, err

    def do_one_error(self, input, expected_err):      
        try:
            res = list(self.eparser.parse(input))
        except expected_err: #I love Python so much. :)
            pass
        else:
        	assert False, res

    def test_retain_if_no_newline(self):
        res = list(self.eparser.parse('foo'))
        assert res == []
        res = list(self.eparser.parse('\n'))
        assert res == ['foo']
    
    def test_chopped_octal_escape(self):
        res = list(self.eparser.parse('foo\\1'))
        assert res == []
        res = list(self.eparser.parse('\n'))
        assert res == ['foo\1']