Adds in support for links

This commit is contained in:
Simon Vermeersch 2014-10-05 17:44:35 +02:00
parent ef23cfceb9
commit cde64692ff
5 changed files with 96 additions and 7 deletions

View file

@ -313,9 +313,9 @@ class MenuNode(object):
choice = "" choice = ""
if self.keywords[ilink]: if self.keywords[ilink]:
if self.keywords[ilink] not in (CMD_NOMATCH, CMD_NOINPUT): if self.keywords[ilink] not in (CMD_NOMATCH, CMD_NOINPUT):
choice += "{g%s{n" % self.keywords[ilink] choice += "{g{lc%s{lt%s{le{n" % (self.keywords[ilink], self.keywords[ilink])
else: else:
choice += "{g %i{n" % (ilink + 1) choice += "{g {lc%i{lt%i{le{n" % ((ilink + 1), (ilink + 1))
if self.linktexts[ilink]: if self.linktexts[ilink]:
choice += " - %s" % self.linktexts[ilink] choice += " - %s" % self.linktexts[ilink]
choices.append(choice) choices.append(choice)

57
src/server/portal/mxp.py Normal file
View file

@ -0,0 +1,57 @@
"""
MXP - Mud eXtension Protocol.
Partial implementation of the MXP protocol.
The MXP protocol allows more advanced formatting options for telnet clients
that supports it (mudlet, zmud, mushclient are a few)
This only implements the SEND tag.
More information can be found on the following links:
http://www.zuggsoft.com/zmud/mxp.htm
http://www.mushclient.com/mushclient/mxp.htm
http://www.gammon.com.au/mushclient/addingservermxp.htm
"""
import re
LINKS_SUB = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
MXP = "\x5B"
MXP_TEMPSECURE = "\x1B[4z"
MXP_SEND = MXP_TEMPSECURE + \
"<SEND HREF='\\1'>" + \
"\\2" + \
MXP_TEMPSECURE + \
"</SEND>"
def mxp_parse(text):
"""
Replaces links to the correct format for MXP.
"""
text = LINKS_SUB.sub(MXP_SEND, text)
return text
class Mxp(object):
"""
Implements the MXP protocol.
"""
def __init__(self, protocol):
"""Initializes the protocol by checking if the client supports it."""
self.protocol = protocol
self.protocol.protocol_flags["MXP"] = False
self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp)
def no_mxp(self, option):
"""
Client does not support MXP.
"""
self.protocol.protocol_flags["MXP"] = False
def do_mxp(self, option):
"""
Client does support MXP.
"""
self.protocol.protocol_flags["MXP"] = True
self.protocol.handshake_done()
self.protocol.requestNegotiation(MXP, '')

View file

@ -12,6 +12,7 @@ from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE,
from src.server.session import Session from src.server.session import Session
from src.server.portal import ttype, mssp, msdp, naws from src.server.portal import ttype, mssp, msdp, naws
from src.server.portal.mccp import Mccp, mccp_compress, MCCP from src.server.portal.mccp import Mccp, mccp_compress, MCCP
from src.server.portal.mxp import MXP, Mxp, mxp_parse
from src.utils import utils, ansi, logger from src.utils import utils, ansi, logger
_RE_N = re.compile(r"\{n$") _RE_N = re.compile(r"\{n$")
@ -47,6 +48,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.mssp = mssp.Mssp(self) self.mssp = mssp.Mssp(self)
# msdp # msdp
self.msdp = msdp.Msdp(self) self.msdp = msdp.Msdp(self)
# mxp support
self.mxp = Mxp(self)
# add this new connection to sessionhandler so # add this new connection to sessionhandler so
# the Server becomes aware of it. # the Server becomes aware of it.
self.sessionhandler.connect(self) self.sessionhandler.connect(self)
@ -199,6 +202,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
given, ttype result is used. If given, ttype result is used. If
client does not suport xterm256, the client does not suport xterm256, the
ansi fallback will be used ansi fallback will be used
mxp=True/False - enforce mxp setting. If not given, enables if we
detected client support for it
ansi=True/False - enforce ansi setting. If not given, ansi=True/False - enforce ansi setting. If not given,
ttype result is used. ttype result is used.
nomarkup=True - strip all ansi markup (this is the same as nomarkup=True - strip all ansi markup (this is the same as
@ -234,6 +239,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
nomarkup = kwargs.get("nomarkup", not (xterm256 or useansi)) nomarkup = kwargs.get("nomarkup", not (xterm256 or useansi))
prompt = kwargs.get("prompt") prompt = kwargs.get("prompt")
echo = kwargs.get("echo", None) echo = kwargs.get("echo", None)
mxp = kwargs.get("mxp", "MXP" in self.protocol_flags)
#print "telnet kwargs=%s, message=%s" % (kwargs, text) #print "telnet kwargs=%s, message=%s" % (kwargs, text)
#print "xterm256=%s, useansi=%s, raw=%s, nomarkup=%s, init_done=%s" % (xterm256, useansi, raw, nomarkup, ttype.get("init_done")) #print "xterm256=%s, useansi=%s, raw=%s, nomarkup=%s, init_done=%s" % (xterm256, useansi, raw, nomarkup, ttype.get("init_done"))
@ -244,7 +250,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# we need to make sure to kill the color at the end in order # we need to make sure to kill the color at the end in order
# to match the webclient output. # to match the webclient output.
#print "telnet data out:", self.protocol_flags, id(self.protocol_flags), id(self), "nomarkup: %s, xterm256: %s" % (nomarkup, xterm256) #print "telnet data out:", self.protocol_flags, id(self.protocol_flags), id(self), "nomarkup: %s, xterm256: %s" % (nomarkup, xterm256)
self.sendLine(ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256)) linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nomarkup, xterm256=xterm256, mxp=mxp)
if mxp:
linetosend = mxp_parse(linetosend)
self.sendLine(linetosend)
if prompt: if prompt:
# Send prompt separately # Send prompt separately

View file

@ -172,7 +172,13 @@ class ANSIParser(object):
""" """
return self.ansi_regex.sub("", string) return self.ansi_regex.sub("", string)
def parse_ansi(self, string, strip_ansi=False, xterm256=False): def strip_mxp(self, string):
"""
Strips all MXP codes from a string.
"""
return self.mxp_sub.sub(r'\2', string)
def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False):
""" """
Parses a string, subbing color codes according to Parses a string, subbing color codes according to
the stored mapping. the stored mapping.
@ -196,6 +202,7 @@ class ANSIParser(object):
return _PARSE_CACHE[cachekey] return _PARSE_CACHE[cachekey]
self.do_xterm256 = xterm256 self.do_xterm256 = xterm256
self.do_mxp = mxp
in_string = utils.to_str(string) in_string = utils.to_str(string)
# do string replacement # do string replacement
@ -209,8 +216,12 @@ class ANSIParser(object):
if strip_ansi: if strip_ansi:
# remove all ansi codes (including those manually # remove all ansi codes (including those manually
# inserted in string) # inserted in string)
parsed_string = self.strip_mxp(parsed_string)
return self.strip_raw_codes(parsed_string) return self.strip_raw_codes(parsed_string)
if not mxp:
parsed_string = self.strip_mxp(parsed_string)
# cache and crop old cache # cache and crop old cache
_PARSE_CACHE[cachekey] = parsed_string _PARSE_CACHE[cachekey] = parsed_string
if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE: if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE:
@ -303,11 +314,14 @@ class ANSIParser(object):
(r'\{\[[0-5]{3}', "") # {[123 - background colour (r'\{\[[0-5]{3}', "") # {[123 - background colour
] ]
mxp_re = r'\{lc(.*?)\{lt(.*?)\{le'
# prepare regex matching # prepare regex matching
#ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1]) #ansi_sub = [(re.compile(sub[0], re.DOTALL), sub[1])
# for sub in ansi_map] # for sub in ansi_map]
xterm256_sub = re.compile(r"|".join([tup[0] for tup in xterm256_map]), re.DOTALL) xterm256_sub = re.compile(r"|".join([tup[0] for tup in xterm256_map]), re.DOTALL)
ansi_sub = re.compile(r"|".join([re.escape(tup[0]) for tup in mux_ansi_map + ext_ansi_map]), re.DOTALL) ansi_sub = re.compile(r"|".join([re.escape(tup[0]) for tup in mux_ansi_map + ext_ansi_map]), re.DOTALL)
mxp_sub = re.compile(mxp_re, re.DOTALL)
# used by regex replacer to correctly map ansi sequences # used by regex replacer to correctly map ansi sequences
ansi_map = dict(mux_ansi_map + ext_ansi_map) ansi_map = dict(mux_ansi_map + ext_ansi_map)
@ -326,12 +340,12 @@ ANSI_PARSER = ANSIParser()
# Access function # Access function
# #
def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False): def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False, mxp=False):
""" """
Parses a string, subbing color codes as needed. Parses a string, subbing color codes as needed.
""" """
return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256) return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256, mxp=mxp)
def strip_raw_ansi(string, parser=ANSI_PARSER): def strip_raw_ansi(string, parser=ANSI_PARSER):

View file

@ -77,6 +77,7 @@ class TextToHTMLparser(object):
re_hilite = re.compile("(?:%s)(.*)(?=%s)" % (hilite.replace("[", r"\["), fgstop)) re_hilite = re.compile("(?:%s)(.*)(?=%s)" % (hilite.replace("[", r"\["), fgstop))
re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[", r"\["), fgstop)) re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[", r"\["), fgstop))
re_string = re.compile(r'(?P<htmlchars>[<&>])|(?P<space> [ \t]+)|(?P<lineend>\r\n|\r|\n)', re.S|re.M|re.I) re_string = re.compile(r'(?P<htmlchars>[<&>])|(?P<space> [ \t]+)|(?P<lineend>\r\n|\r|\n)', re.S|re.M|re.I)
re_link = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
def re_color(self, text): def re_color(self, text):
""" """
@ -119,6 +120,13 @@ class TextToHTMLparser(object):
# change pages (and losing our webclient session). # change pages (and losing our webclient session).
return re.sub(regexp, r'<a href="\1" target="_blank">\1</a>', text) return re.sub(regexp, r'<a href="\1" target="_blank">\1</a>', text)
def convert_links(self, text):
"""
Replaces links with HTML code
"""
html = "<a href='#' onclick='websocket.send(\"\\1\"); return false;'>\\2</a>"
return self.re_link.sub(html, text)
def do_sub(self, m): def do_sub(self, m):
"Helper method to be passed to re.sub." "Helper method to be passed to re.sub."
c = m.groupdict() c = m.groupdict()
@ -139,7 +147,7 @@ class TextToHTMLparser(object):
ansi codes into html statements. ansi codes into html statements.
""" """
# parse everything to ansi first # parse everything to ansi first
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=False) text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=False, mxp=True)
# convert all ansi to html # convert all ansi to html
result = re.sub(self.re_string, self.do_sub, text) result = re.sub(self.re_string, self.do_sub, text)
result = self.re_color(result) result = self.re_color(result)
@ -149,6 +157,7 @@ class TextToHTMLparser(object):
result = self.convert_linebreaks(result) result = self.convert_linebreaks(result)
result = self.remove_backspaces(result) result = self.remove_backspaces(result)
result = self.convert_urls(result) result = self.convert_urls(result)
result = self.convert_links(result)
# clean out eventual ansi that was missed # clean out eventual ansi that was missed
#result = parse_ansi(result, strip_ansi=True) #result = parse_ansi(result, strip_ansi=True)