import re import random from copy import copy from datetime import timedelta from PyQt4 import QtGui from generic import mysteryTime _ctag_begin = re.compile(r'(?i)') _gtag_begin = re.compile(r'(?i)') _ctag_end = re.compile(r'(?i)') _ctag_rgb = re.compile(r'\d+,\d+,\d+') _urlre = re.compile(r"(?i)https?://[^\s]+") _memore = re.compile(r"(\s|^)(#[A-Za-z0-9_]+)") _handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)") _imgre = re.compile(r"""(?i)""") _mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)") _functionre = re.compile(r"(upper\(|lower\(|scramble\(|reverse\(|\)|\\[0-9]+)") _groupre = re.compile(r"\\([0-9]+)") def lexer(string, objlist): """objlist is a list: [(objecttype, re),...] list is in order of preference""" stringlist = [string] for (oType, regexp) in objlist: newstringlist = [] for (stri, s) in enumerate(stringlist): if type(s) not in [str, unicode]: newstringlist.append(s) continue lasti = 0 for m in regexp.finditer(s): start = m.start() end = m.end() tag = oType(m.group(0), *m.groups()) if lasti != start: newstringlist.append(s[lasti:start]) newstringlist.append(tag) lasti = end if lasti < len(string): newstringlist.append(s[lasti:]) stringlist = copy(newstringlist) return stringlist class colorBegin(object): def __init__(self, string, color): self.string = string self.color = color def convert(self, format): color = self.color if format == "text": return "" if _ctag_rgb.match(color) is not None: if format=='ctag': return "" % (color) try: qc = QtGui.QColor(*[int(c) for c in color.split(",")]) except ValueError: qc = QtGui.QColor("black") else: qc = QtGui.QColor(color) if not qc.isValid(): qc = QtGui.QColor("black") if format == "html": return '' % (qc.name()) elif format == "bbcode": return '[color=%s]' % (qc.name()) elif format == "ctag": (r,g,b,a) = qc.getRgb() return '' % (r,g,b) class colorEnd(object): def __init__(self, string): self.string = string def convert(self, format): if format == "html": return "" elif format == "bbcode": return "[/color]" elif format == "text": return "" else: return self.string class hyperlink(object): def __init__(self, string): self.string = string def convert(self, format): if format == "html": return "%s" % (self.string, self.string) elif format == "bbcode": return "[url]%s[/url]" % (self.string) else: return self.string class imagelink(object): def __init__(self, string, img): self.string = string self.img = img def convert(self, format): if format == "html": return self.string elif format == "bbcode": if self.img[0:7] == "http://": return "[img]%s[/img]" % (self.img) else: return "" else: return "" class memolex(object): def __init__(self, string, space, channel): self.string = string self.space = space self.channel = channel def convert(self, format): if format == "html": return "%s%s" % (self.space, self.channel, self.channel) else: return self.string class chumhandlelex(object): def __init__(self, string, space, handle): self.string = string self.space = space self.handle = handle def convert(self, format): if format == "html": return "%s%s" % (self.space, self.handle, self.handle) else: return self.string class smiley(object): def __init__(self, string): self.string = string def convert(self, format): if format == "html": return "%s" % (smiledict[self.string], self.string, self.string) else: return self.string class mecmd(object): def __init__(self, string, mecmd, suffix): self.string = string self.suffix = suffix def convert(self, format): return self.string def lexMessage(string): lexlist = [(mecmd, _mecmdre), (colorBegin, _ctag_begin), (colorBegin, _gtag_begin), (colorEnd, _ctag_end), (imagelink, _imgre), (hyperlink, _urlre), (memolex, _memore), (chumhandlelex, _handlere), (smiley, _smilere)] string = unicode(string) string = string.replace("\n", " ").replace("\r", " ") lexed = lexer(unicode(string), lexlist) balanced = [] beginc = 0 endc = 0 for o in lexed: if type(o) is colorBegin: beginc += 1 balanced.append(o) elif type(o) is colorEnd: if beginc >= endc: endc += 1 balanced.append(o) else: balanced.append(o.string) else: balanced.append(o) if beginc > endc: for i in range(0, beginc-endc): balanced.append(colorEnd("")) if len(balanced) == 0: balanced.append("") if type(balanced[len(balanced)-1]) not in [str, unicode]: balanced.append("") return balanced def convertTags(lexed, format="html"): if format not in ["html", "bbcode", "ctag", "text"]: raise ValueError("Color format not recognized") if type(lexed) in [str, unicode]: lexed = lexMessage(lexed) escaped = "" firststr = True for (i, o) in enumerate(lexed): if type(o) in [str, unicode]: if format == "html": escaped += o.replace("&", "&").replace(">", ">").replace("<","<") else: escaped += o else: escaped += o.convert(format) return escaped def splitMessage(msg, format="ctag"): """Splits message if it is too long.""" # split long text lines buf = [] for o in msg: if type(o) in [str, unicode] and len(o) > 200: for i in range(0, len(o), 200): buf.append(o[i:i+200]) else: buf.append(o) msg = buf okmsg = [] cbegintags = [] output = [] for o in msg: oldctag = None okmsg.append(o) if type(o) is colorBegin: cbegintags.append(o) elif type(o) is colorEnd: try: oldctag = cbegintags.pop() except IndexError: pass # yeah normally i'd do binary search but im lazy msglen = len(convertTags(okmsg, format)) + 4*(len(cbegintags)) if msglen > 400: okmsg.pop() if type(o) is colorBegin: cbegintags.pop() elif type(o) is colorEnd and oldctag is not None: cbegintags.append(oldctag) if len(okmsg) == 0: output.append([o]) else: tmp = [] for color in cbegintags: okmsg.append(colorEnd("")) tmp.append(color) output.append(okmsg) if type(o) is colorBegin: cbegintags.append(o) elif type(o) is colorEnd: try: cbegintags.pop() except IndexError: pass tmp.append(o) okmsg = tmp if len(okmsg) > 0: output.append(okmsg) return output def addTimeInitial(string, grammar): endofi = string.find(":") endoftag = string.find(">") # support Doc Scratch mode if (endoftag < 0 or endoftag > 16) or (endofi < 0 or endofi > 17): return string return string[0:endoftag+1]+grammar.pcf+string[endoftag+1:endofi]+grammar.number+string[endofi:] def timeProtocol(cmd): dir = cmd[0] if dir == "?": return mysteryTime(0) cmd = cmd[1:] cmd = re.sub("[^0-9:]", "", cmd) try: l = [int(x) for x in cmd.split(":")] except ValueError: l = [0,0] timed = timedelta(0, l[0]*3600+l[1]*60) if dir == "P": timed = timed*-1 return timed def timeDifference(td): if type(td) is mysteryTime: return "??:?? FROM ????" if td < timedelta(0): when = "AGO" else: when = "FROM NOW" atd = abs(td) minutes = (atd.days*86400 + atd.seconds) // 60 hours = minutes // 60 leftoverminutes = minutes % 60 if atd == timedelta(0): timetext = "RIGHT NOW" elif atd < timedelta(0,3600): if minutes == 1: timetext = "%d MINUTE %s" % (minutes, when) else: timetext = "%d MINUTES %s" % (minutes, when) elif atd < timedelta(0,3600*100): if hours == 1 and leftoverminutes == 0: timetext = "%d:%02d HOUR %s" % (hours, leftoverminutes, when) else: timetext = "%d:%02d HOURS %s" % (hours, leftoverminutes, when) else: timetext = "%d HOURS %s" % (hours, when) return timetext def upperrep(text): return text.upper() def lowerrep(text): return text.lower() def scramblerep(text): return "".join(random.sample(text, len(text))) def reverserep(text): return text[::-1] def nonerep(text): return text class parseLeaf(object): def __init__(self, function, parent): self.nodes = [] self.function = function self.parent = parent def append(self, node): self.nodes.append(node) def expand(self, mo): out = "" for n in self.nodes: if type(n) == parseLeaf: out += n.expand(mo) elif type(n) == backreference: out += mo.group(int(n.number)) else: out += n out = self.function(out) return out class backreference(object): def __init__(self, number): self.number = number def __str__(self): return self.number def parseRegexpFunctions(to): parsed = parseLeaf(nonerep, None) current = parsed curi = 0 functiondict = {"upper(": upperrep, "lower(": lowerrep, "scramble(": scramblerep, "reverse(": reverserep} while curi < len(to): tmp = to[curi:] mo = _functionre.search(tmp) if mo is not None: if mo.start() > 0: current.append(to[curi:curi+mo.start()]) backr = _groupre.search(mo.group()) if backr is not None: current.append(backreference(backr.group(1))) elif mo.group() in functiondict.keys(): p = parseLeaf(functiondict[mo.group()], current) current.append(p) current = p elif mo.group() == ")": if current.parent is not None: current = current.parent else: current.append(")") curi = mo.end()+curi else: current.append(to[curi:]) curi = len(to) return parsed def img2smiley(string): string = unicode(string) def imagerep(mo): return reverse_smiley[mo.group(1)] string = re.sub(r'', imagerep, string) return string smiledict = { ":rancorous:": "pc_rancorous.gif", ":apple:": "apple.gif", ":bathearst:": "bathearst.gif", ":cathearst:": "cathearst.png", ":woeful:": "pc_bemused.gif", ":sorrow:": "blacktear.gif", ":pleasant:": "pc_pleasant.gif", ":blueghost:": "blueslimer.gif", ":slimer:": "slimer.gif", ":candycorn:": "candycorn.gif", ":cheer:": "cheer.gif", ":duhjohn:": "confusedjohn.gif", ":datrump:": "datrump.gif", ":facepalm:": "facepalm.gif", ":bonk:": "headbonk.gif", ":mspa:": "mspa_face.gif", ":gun:": "mspa_reader.gif", ":cal:": "lilcal.png", ":amazedfirman:": "pc_amazedfirman.gif", ":amazed:": "pc_amazed.gif", ":chummy:": "pc_chummy.gif", ":cool:": "pccool.gif", ":smooth:": "pccool.gif", ":distraughtfirman": "pc_distraughtfirman.gif", ":distraught:": "pc_distraught.gif", ":insolent:": "pc_insolent.gif", ":bemused:": "pc_bemused.gif", ":3:": "pckitty.gif", ":mystified:": "pc_mystified.gif", ":pranky:": "pc_pranky.gif", ":tense:": "pc_tense.gif", ":record:": "record.gif", ":squiddle:": "squiddle.gif", ":tab:": "tab.gif", ":beetip:": "theprofessor.gif", ":flipout:": "weasel.gif", ":befuddled:": "what.gif", ":pumpkin:": "whatpumpkin.gif", ":trollcool:": "trollcool.gif", ":jadecry:": "jadespritehead.gif", ":ecstatic:": "ecstatic.png", ":relaxed:": "relaxed.png", ":discontent:": "discontent.png", ":devious:": "devious.png", ":sleek:": "sleek.png", ":detestful:": "detestful.png", ":mirthful:": "mirthful.png", ":manipulative:": "manipulative.png", ":vigorous:": "vigorous.png", ":perky:": "perky.png", ":acceptant:": "acceptant.gif", } reverse_smiley = dict((v,k) for k, v in smiledict.iteritems()) _smilere = re.compile("|".join(smiledict.keys())) class ThemeException(Exception): def __init__(self, value): self.parameter = value def __str__(self): return repr(self.parameter) def themeChecker(theme): needs = ["main/size", "main/icon", "main/windowtitle", "main/style", \ "main/background-image", "main/menubar/style", "main/menu/menuitem", \ "main/menu/style", "main/menu/selected", "main/close/image", \ "main/close/loc", "main/minimize/image", "main/minimize/loc", \ "main/menu/loc", "main/menus/client/logviewer", \ "main/menus/client/addgroup", "main/menus/client/options", \ "main/menus/client/exit", "main/menus/client/userlist", \ "main/menus/client/memos", "main/menus/client/import", \ "main/menus/client/idle", "main/menus/client/reconnect", \ "main/menus/client/_name", "main/menus/profile/quirks", \ "main/menus/profile/block", "main/menus/profile/color", \ "main/menus/profile/switch", "main/menus/profile/_name", \ "main/menus/help/about", "main/menus/help/_name", "main/moodlabel/text", \ "main/moodlabel/loc", "main/moodlabel/style", "main/moods", \ "main/addchum/style", "main/addchum/text", "main/addchum/size", \ "main/addchum/loc", "main/pester/text", "main/pester/size", \ "main/pester/loc", "main/block/text", "main/block/size", "main/block/loc", \ "main/mychumhandle/label/text", "main/mychumhandle/label/loc", \ "main/mychumhandle/label/style", "main/mychumhandle/handle/loc", \ "main/mychumhandle/handle/size", "main/mychumhandle/handle/style", \ "main/mychumhandle/colorswatch/size", "main/mychumhandle/colorswatch/loc", \ "main/defaultmood", "main/chums/size", "main/chums/loc", \ "main/chums/style", "main/menus/rclickchumlist/pester", \ "main/menus/rclickchumlist/removechum", \ "main/menus/rclickchumlist/blockchum", "main/menus/rclickchumlist/viewlog", \ "main/menus/rclickchumlist/removegroup", \ "main/menus/rclickchumlist/renamegroup", \ "main/menus/rclickchumlist/movechum", "convo/size", \ "convo/tabwindow/style", "convo/tabs/tabstyle", "convo/tabs/style", \ "convo/tabs/selectedstyle", "convo/style", "convo/margins", \ "convo/chumlabel/text", "convo/chumlabel/style", "convo/chumlabel/align/h", \ "convo/chumlabel/align/v", "convo/chumlabel/maxheight", \ "convo/chumlabel/minheight", "main/menus/rclickchumlist/quirksoff", \ "main/menus/rclickchumlist/addchum", "main/menus/rclickchumlist/blockchum", \ "main/menus/rclickchumlist/unblockchum", \ "main/menus/rclickchumlist/viewlog", "main/trollslum/size", \ "main/trollslum/style", "main/trollslum/label/text", \ "main/trollslum/label/style", "main/menus/profile/block", \ "main/chums/moods/blocked/icon", "convo/systemMsgColor", \ "convo/textarea/style", "convo/text/beganpester", "convo/text/ceasepester", \ "convo/text/blocked", "convo/text/unblocked", "convo/text/blockedmsg", \ "convo/text/idle", "convo/input/style", "memos/memoicon", \ "memos/textarea/style", "memos/systemMsgColor", "convo/text/joinmemo", \ "memos/input/style", "main/menus/rclickchumlist/banuser", \ "main/menus/rclickchumlist/opuser", "main/menus/rclickchumlist/voiceuser", \ "memos/margins", "convo/text/openmemo", "memos/size", "memos/style", \ "memos/label/text", "memos/label/style", "memos/label/align/h", \ "memos/label/align/v", "memos/label/maxheight", "memos/label/minheight", \ "memos/userlist/style", "memos/userlist/width", "memos/time/text/width", \ "memos/time/text/style", "memos/time/arrows/left", \ "memos/time/arrows/style", "memos/time/buttons/style", \ "memos/time/arrows/right", "memos/op/icon", "memos/voice/icon", \ "convo/text/closememo", "convo/text/kickedmemo", \ "main/chums/userlistcolor", "main/defaultwindow/style", \ "main/chums/moods", "main/chums/moods/chummy/icon", "main/menus/help/help", \ "main/menus/help/calsprite", "main/menus/help/nickserv", "main/menus/rclickchumlist/invitechum"] for n in needs: try: theme[n] except KeyError: raise ThemeException("Missing theme requirement: %s" % (n))