# chatola.py # a simple chat engine from twisted.internet.protocol import ClientCreator, Factory from twisted.protocols.basic import LineReceiver from twisted.python import failure from twisted.cred import portal, checkers, credentials, error from twisted.web import resource from twisted.internet import reactor, defer from nevow import renderer from nevow.tags import * from nevow import liveevil from nevow import guard class IMooCredentials(credentials.ICredentials): pass class MooCredentials: __implements__ = IMooCredentials, def __init__(self, server, port, username, password): self.server = server self.port = port self.username = username self.password = password class MooCredChecker: credentialInterfaces = (IMooCredentials, ) def requestAvatarId(self, credentials): try: port = int(credentials.port) except: return failure.Failure(error.UnauthorizedLogin("Port should be an integer.")) return ClientCreator( reactor, MooProtocol, credentials.username, credentials.password ).connectTCP( credentials.server, port ).addCallback( lambda proto: proto.finishedConnecting.addErrback( lambda reason: failure.Failure( error.UnauthorizedLogin("Login failed: %s" % reason)))) class MooGuard(guard.SessionWrapper): def getCredentials(self, request): arg = lambda S: request.args.get(S)[0] return MooCredentials( arg('server'), arg('port'), arg('user'), arg('password') ) class MooseRealm: """A simple implementor of cred's IRealm. The only interface we support web, this gives us the LoggedIn page. """ __implements__ = portal.IRealm def requestAvatar(self, avatarId, mind, *interfaces): if resource.IResource in interfaces: if avatarId is checkers.ANONYMOUS: return ( resource.IResource, Anonymous(), lambda: None) else: page = MooseClient(avatarId, mind) return resource.IResource, page, lambda: None raise NotImplementedError("Can't support that interface.") class MooProtocol(LineReceiver): def __init__(self, username, password): self.username = username self.password = password self.buffer = [] self.authenticated = False self.outputConduit = None self.finishedConnecting = defer.Deferred() def connectionLost(self, reason): if not self.authenticated: self.finishedConnecting.errback('Connection was dropped.') def lineReceived(self, line): if not self.authenticated: if line.startswith('The lag is'): self.transport.write("co %s %s\r\n" % (self.username, self.password)) if line.startswith('*** Connected ***'): self.authenticated = True self.finishedConnecting.callback(self) elif line.startswith('Either that player does not exist, or has a different password'): self.finishedConnecting.errback('Username or password incorrect') self.transport.loseConnection() if self.outputConduit is None: self.buffer.append(line) else: self.outputConduit.write(line) def outputConnected(self, output): self.outputConduit = output for line in self.buffer: output.write(line) self.buffer = [] class Anonymous(renderer.Renderer): def render_login(self, context, data): return form(action=guard.LOGIN_AVATAR)[ div[label["Server:"], input(type="text", name="server", value="pictwe.com")], div[label["Port:"], input(type="text", name="port", value="4242")], div[label["Username:"], input(type="text", name="user", value="fzzzy")], div[label["Password:"], input(type="password", name="password", value="")], input(type="submit", name="foo", value="bar") ] document = html[ head[ title["log in"] ], body[ render_login ] ] q = lambda it: '"%s"' % it class MooseClient(renderer.HTMLRenderer): templateFile = "Moose.html" client = None # The client will be set to an object which we can use to send and recieve data from the web browser. def __init__(self, connection, client, *args, **kw): renderer.HTMLRenderer.__init__(self, *args, **kw) self.client = client client.addNotification(lambda output: connection.outputConnected(self)) self.connection = connection def render_glue(self, context, data): return liveevil.glue def render_content(self, context, data): return context.tag[div(style="height: 100%")[xml(" ")]] def render_input(self, context, data): return form( style="margin: 1em;", onsubmit=liveevil.handler(self.onTextInput, "getInput();") )[ input(type="text", id="inputline", style="width: 100%") ] def onTextInput(self, client, text): self.connection.transport.write(text+'\r\n') self.client.sendScript("writeContent('
-> %s
');" % text) def render_examine(self, context, data): return form(onsubmit=liveevil.handler(self.onExamine, "getValue('examineWhat')"))[ div(id="examineOutput"), input(type="text", id="examineWhat", width="100%") ] def onExamine(self, client, examineWhat): self.doOutOfBand(self.examineResults, 'object_info', OBJ_=examineWhat) def examineResults(self, client, OBJ_, VERBS_, PROPS_, OBJ_NAME_): html = [ strong[OBJ_NAME_], "(", em[OBJ_], ")", br, strong["Verbs"], ul[[ li[ a(onclick=liveevil.handler( self.onVerb, q(v), q(OBJ_[0]), q(OBJ_NAME_[0])) )[v] ] for v in VERBS_ ]], strong["Properties"], ul[[ li[p] for p in PROPS_ ]] ] print "HTML", html self.examineOutput(client, html) def examineOutput(self, client, html): client.sendScript("document.getElementById('examineOutput').innerHTML = '%s';" % self.flatten(client, html)) def flatten(self, client, html): from nevow import iwoven from nevow import context ctx = context.WovenContext() ctx.remember(None, iwoven.IData) ctx.remember(client, liveevil.ILiveEvil) html = ''.join(renderer.flatten(renderer.serialize(html, ctx))) html = html.replace("'", "\\'").replace('"', '\\"') return html def onVerb(self, client, CODE_NAME_, OBJ_, OBJ_NAME_): self.verbName = CODE_NAME_ self.objName = OBJ_NAME_ self.doOutOfBand(self.examineVerb, 'list_code', CODE_NAME_=CODE_NAME_, OBJ_=OBJ_, CODE_TYPE_="VERB", OBJ_NAME_=OBJ_NAME_) def examineVerb(self, client, line=None, end=False, **kw): if not end: if line is None: self.curMetadata = kw self.curCode = [] else: self.curCode.append(line) else: print "END", self.curMetadata, self.curCode md = self.curMetadata html = [ strong[self.objName, ':', self.verbName], '(', em[md['VERB_DOBJ_'][0], ' ', md['VERB_PREP_'][0], ' ', md['VERB_IOBJ_'][0]], ') ', md['VERB_PERMS_'] ] if md['VERB_CHANGEABLE_'][0] == '1': html.append( textarea['\\n'.join(self.curCode)] ) else: html.append( [ pre[x] for x in self.curCode ] ) client.sendScript("document.getElementById('examineOutput').innerHTML = '%s';" % self.flatten(client, html)) def doOutOfBand(self, handler, command, **args): self.currentHandler = handler argstr = ' '.join([ '%s: %s' % (key, value) for (key, value) in args.items() ]) oob = '#$#MacMOOSE %s %s\r\n' % (command, argstr) print "OOB", oob self.connection.transport.write(oob) def write(self, text): """Write some text to the client browser. """ if text.startswith('_&_'): print "IB", text result = text[4:] if result.startswith('CODE_LINE_: '): return self.currentHandler(self.client, result[len('CODE_LINE_: '):]) elif result.startswith('CODE_END'): return self.currentHandler(self.client, end=True) pairs = result.split() keywords = {} while pairs: key = pairs.pop(0)[:-1] val = pairs.pop(0) ## Null can't possibly be in the strings that came across the wire, so we will use it ## to mark where the real forward slashes are supposed to go. val = val.replace(r'\\/', '\0').split('/') val = [x.replace('\0', '/') for x in val if x] keywords[key] = val self.currentHandler(self.client, **keywords) else: safe = text.replace("'", "\\'").replace('"', '\\"') self.client.sendScript("writeContent('
%s
');" % safe)