version.py
USER_TYPE = "user"
INSTALL_TYPE = "installer"
OS_TYPE = sys.platform # win32, linux, darwin
    OS_TYPE = "linux"
    OS_TYPE = "mac"
def pcVerCalc():
def lexVersion(short=False):
def verStrToNum(ver):
def updateCheck(q):
def removeCopies(path):
def copyUpdate(path):
def updateExtract(url, extension):
def updateDownload(url):
dataobjs.py
class pesterQuirks(object):
    def __init__(self, quirklist):
        self.quirklist = []
        for q in quirklist:
            self.addQuirk(q)
    def plainList(self):
        return [q.quirk for q in self.quirklist]
    def addQuirk(self, q):
        if type(q) == dict:
            self.quirklist.append(pesterQuirk(q))
        elif type(q) == pesterQuirk:
            self.quirklist.append(q)
    def apply(self, lexed, first=False, last=False):
        prefix = [q for q in self.quirklist if q.type=='prefix']
        suffix = [q for q in self.quirklist if q.type=='suffix']

        newlist = []
        for (i, o) in enumerate(lexed):
            if type(o) not in [str]:
                if i == 0:
                    string = " "
                    for p in prefix:
                        string += p.apply(string)
                    newlist.append(string)
                newlist.append(o)
                continue
            lastStr = (i == len(lexed)-1)
            string = o
            for q in self.quirklist:
                if q.type != 'prefix' and q.type != 'suffix':
                    if q.type == 'regexp' or q.type == 'random':
                        string = q.apply(string, first=(i==0), last=lastStr)
                    else:
                        string = q.apply(string)
                elif q.type == 'prefix' and i == 0:
                    string = q.apply(string)
                elif q.type == 'suffix' and lastStr:
                    string = q.apply(string)
            newlist.append(string)

        final = []
        for n in newlist:
            if type(n) in [str]:
                final.extend(lexMessage(n))
            else:
                final.append(n)
        return final

    def __iter__(self):
        for q in self.quirklist:
            yield q
parsetools.py
def reloadQuirkFunctions():
def lexer(string, objlist):
    def __init__(self, string, color):
    def convert(self, format):
    def __init__(self, string):
    def convert(self, format):
    def __init__(self, string, ftype):
    def convert(self, format):
    def __init__(self, string, ftype):
    def convert(self, format):
    def __init__(self, string):
    def convert(self, format):
    def __init__(self, string):
    def __init__(self, string, img):
    def convert(self, format):
    def __init__(self, string, space, channel):
    def convert(self, format):
    def __init__(self, string, space, handle):
    def convert(self, format):
    def __init__(self, string):
    def convert(self, format):
    def __init__(self, string):
    def convert(self, format):
    def __init__(self, string, mecmd, suffix):
    def convert(self, format):
def lexMessage(string):
def convertTags(lexed, format="html"):
def splitMessage(msg, format="ctag"):
def addTimeInitial(string, grammar):
def timeProtocol(cmd):
def timeDifference(td):
def nonerep(text):
    def __init__(self, function, parent):
    def append(self, node):
    def expand(self, mo):
    def __init__(self, number):
    def __str__(self):
def parseRegexpFunctions(to):
def img2smiley(string):
    def imagerep(mo):
    def __init__(self, value):
    def __str__(self):
def themeChecker(theme):
class colorBegin(object):
class colorEnd(object):
class formatBegin(object):
class formatEnd(object):
class hyperlink(object):
class hyperlink_lazy(hyperlink):
class imagelink(object):
class memolex(object):
class chumhandlelex(object):
class smiley(object):
class honker(object):
class mecmd(object):
class parseLeaf(object):
class backreference(object):
class ThemeException(Exception):
generic.py
class RightClickTree(QtWidgets.QTreeWidget):
    def contextMenuEvent(self, event):
        if event.reason() == QtGui.QContextMenuEvent.Mouse:
            listing = self.itemAt(event.pos())
            self.setCurrentItem(listing)
            optionsMenu = self.getOptionsMenu()
            if optionsMenu:
                optionsMenu.popup(event.globalPos())
    def getOptionsMenu(self):
        return self.optionsMenu
convo.py
class PesterText(QtWidgets.QTextEdit):
    def __init__(self, theme, parent=None):
        QtWidgets.QTextEdit.__init__(self, parent)
        if hasattr(self.parent(), 'mainwindow'):
            self.mainwindow = self.parent().mainwindow
        else:
            self.mainwindow = self.parent()
        if type(parent.parent()) is PesterTabWindow:
            self.tabobject = parent.parent()
            self.hasTabs = True
        else:
            self.hasTabs = False
        self.initTheme(theme)
        self.setReadOnly(True)
        self.setMouseTracking(True)
        self.textSelected = False
        self.copyAvailable.connect(self.textReady)
        self.urls = {}
        for k in smiledict:
            self.addAnimation(QtCore.QUrl("smilies/%s" % (smiledict[k])), "smilies/%s" % (smiledict[k]))
        self.mainwindow.animationSetting.connect(self.animateChanged)
    def addAnimation(self, url, fileName):
        movie = PesterMovie(self)
        movie.setFileName(fileName)
        movie.setCacheMode(QtGui.QMovie.CacheAll)
        if movie.frameCount() > 1:
            self.urls[movie] = url
            movie.frameChanged.connect(movie.animate)
            #movie.start()
    @QtCore.pyqtSlot(bool)
    def animateChanged(self, animate):
        if animate:
            for m in self.urls:
                html = str(self.toHtml())
                if html.find(self.urls[m].toString()) != -1:
                    if m.frameCount() > 1:
                        m.start()
        else:
            for m in self.urls:
                html = str(self.toHtml())
                if html.find(self.urls[m].toString()) != -1:
                    if m.frameCount() > 1:
                        m.stop()

    @QtCore.pyqtSlot(bool)
    def textReady(self, ready):
        self.textSelected = ready
    def initTheme(self, theme):
        if "convo/scrollbar" in theme:
            self.setStyleSheet("QTextEdit { %s } QScrollBar:vertical { %s } QScrollBar::handle:vertical { %s } QScrollBar::add-line:vertical { %s } QScrollBar::sub-line:vertical { %s } QScrollBar:up-arrow:vertical { %s } QScrollBar:down-arrow:vertical { %s }" % (theme["convo/textarea/style"], theme["convo/scrollbar/style"], theme["convo/scrollbar/handle"], theme["convo/scrollbar/downarrow"], theme["convo/scrollbar/uparrow"], theme["convo/scrollbar/uarrowstyle"], theme["convo/scrollbar/darrowstyle"] ))
        else:
            self.setStyleSheet("QTextEdit { %s }" % (theme["convo/textarea/style"]))
    def addMessage(self, lexmsg, chum):
        if len(lexmsg) == 0:
            return
        color = chum.colorcmd()
        systemColor = QtGui.QColor(self.parent().mainwindow.theme["convo/systemMsgColor"])
        initials = chum.initials()
        parent = self.parent()
        window = parent.mainwindow
        me = window.profile()
        if self.mainwindow.config.animations():
            for m in self.urls:
                if convertTags(lexmsg).find(self.urls[m].toString()) != -1:
                    if m.state() == QtGui.QMovie.NotRunning:
                        m.start()
        if self.parent().mainwindow.config.showTimeStamps():
            if self.parent().mainwindow.config.time12Format():
                time = strftime("[%I:%M")
            else:
                time = strftime("[%H:%M")
            if self.parent().mainwindow.config.showSeconds():
                time += strftime(":%S] ")
            else:
                time += "] "
        else:
            time = ""
        if lexmsg[0] == "PESTERCHUM:BEGIN":
            parent.setChumOpen(True)
            pmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/beganpester"])
            window.chatlog.log(chum.handle, pmsg)
            self.append(convertTags(pmsg))
        elif lexmsg[0] == "PESTERCHUM:CEASE":
            parent.setChumOpen(False)
            pmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/ceasepester"])
            window.chatlog.log(chum.handle, pmsg)
            self.append(convertTags(pmsg))
        elif lexmsg[0] == "PESTERCHUM:BLOCK":
            pmsg = chum.pestermsg(me, systemColor, window.theme['convo/text/blocked'])
            window.chatlog.log(chum.handle, pmsg)
            self.append(convertTags(pmsg))
        elif lexmsg[0] == "PESTERCHUM:UNBLOCK":
            pmsg = chum.pestermsg(me, systemColor, window.theme['convo/text/unblocked'])
            window.chatlog.log(chum.handle, pmsg)
            self.append(convertTags(pmsg))
        elif lexmsg[0] == "PESTERCHUM:BLOCKED":
            pmsg = chum.pestermsg(me, systemColor, window.theme['convo/text/blockedmsg'])
            window.chatlog.log(chum.handle, pmsg)
            self.append(convertTags(pmsg))
        elif lexmsg[0] == "PESTERCHUM:IDLE":
            imsg = chum.idlemsg(systemColor, window.theme['convo/text/idle'])
            window.chatlog.log(chum.handle, imsg)
            self.append(convertTags(imsg))
        elif type(lexmsg[0]) is mecmd:
            memsg = chum.memsg(systemColor, lexmsg)
            if chum is me:
                window.chatlog.log(parent.chum.handle, memsg)
            else:
                window.chatlog.log(chum.handle, memsg)
            self.append(time + convertTags(memsg))
        else:
            if not parent.chumopen and chum is not me:
                beginmsg = chum.pestermsg(me, systemColor, window.theme["convo/text/beganpester"])
                parent.setChumOpen(True)
                window.chatlog.log(chum.handle, beginmsg)
                self.append(convertTags(beginmsg))

            lexmsg[0:0] = [colorBegin("<c=%s>" % (color), color),
                           "%s: " % (initials)]
            lexmsg.append(colorEnd("</c>"))
            self.append("<span style=\"color:#000000\">" + time + convertTags(lexmsg) + "</span>")
            #self.append('<img src="/Users/lexi/pesterchum-lex/smilies/tab.gif" />'
            #            + '<img src="/Users/lexi/pesterchum/smilies/tab.gif" />'
            #            + '<img src="/Applications/Pesterchum.app/Contents/Resources/smilies/tab.gif" />'
            #            + '<img src="smilies/tab.gif" />');
            if chum is me:
                window.chatlog.log(parent.chum.handle, lexmsg)
            else:
                if window.idleaction.isChecked() and parent.chumopen:
                    idlethreshhold = 60
                    if (not hasattr(self, 'lastmsg')) or \
                            datetime.now() - self.lastmsg > timedelta(0,idlethreshhold):
                        verb = window.theme["convo/text/idle"]
                        idlemsg = me.idlemsg(systemColor, verb)
                        parent.textArea.append(convertTags(idlemsg))
                        window.chatlog.log(parent.title(), idlemsg)
                        parent.messageSent.emit("PESTERCHUM:IDLE", parent.title())
                self.lastmsg = datetime.now()
                window.chatlog.log(chum.handle, lexmsg)
    def changeTheme(self, theme):
        self.initTheme(theme)
        sb = self.verticalScrollBar()
        sb.setValue(sb.maximum())
    def focusInEvent(self, event):
        self.parent().clearNewMessage()
        QtWidgets.QTextEdit.focusInEvent(self, event)

    def keyPressEvent(self, event):
        if hasattr(self.parent(), 'textInput'):
            if event.key() not in [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown, \
                                   QtCore.Qt.Key_Up, QtCore.Qt.Key_Down]:
                self.parent().textInput.keyPressEvent(event)
        QtWidgets.QTextEdit.keyPressEvent(self, event)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            url = self.anchorAt(event.pos())
            if url != "":
                if url[0] == "#" and url != "#pesterchum":
                    self.parent().mainwindow.showMemos(url[1:])
                elif url[0] == "@":
                    handle = str(url[1:])
                    self.parent().mainwindow.newConversation(handle)
                else:
                    if event.modifiers() == QtCore.Qt.ControlModifier:
                        QtWidgets.QApplication.clipboard().setText(url)
                    else:
                        QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))
        QtWidgets.QTextEdit.mousePressEvent(self, event)
    def mouseMoveEvent(self, event):
        QtWidgets.QTextEdit.mouseMoveEvent(self, event)
        if self.anchorAt(event.pos()):
            if self.viewport().cursor().shape != QtCore.Qt.PointingHandCursor:
                self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        else:
            self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))

    def contextMenuEvent(self, event):
        textMenu = self.createStandardContextMenu()
        if self.textSelected:
            self.submitLogAction = QtWidgets.QAction("Submit to Pesterchum QDB", self, triggered=self.submitLog)
            textMenu.addAction(self.submitLogAction)
        textMenu.exec_(event.globalPos())

    def submitLogTitle(self):
        return "[%s -> %s]" % (self.parent().mainwindow.profile().handle,
                               self.parent().chum.handle)

    @QtCore.pyqtSlot()
    def submitLog(self):
        mimedata = self.createMimeDataFromSelection()
        htmldata = img2smiley(mimedata.data("text/html"))
        textdoc = QtGui.QTextDocument()
        textdoc.setHtml(htmldata)
        logdata = "%s\n%s" % (self.submitLogTitle(), textdoc.toPlainText())
        self.sending = QtWidgets.QDialog(self)
        layout = QtWidgets.QVBoxLayout()
        self.sending.sendinglabel = QtWidgets.QLabel("S3ND1NG...", self.sending)
        cancelbutton = QtWidgets.QPushButton("OK", self.sending, clicked=self.sending.close)
        layout.addWidget(self.sending.sendinglabel)
        layout.addWidget(cancelbutton)
        self.sending.setLayout(layout)
        self.sending.show()
        params = urllib.parse.urlencode({'quote': logdata, 'do': "add"})
        headers = {"Content-type": "application/x-www-form-urlencoded",
                   "Accept": "text/plain"}
        try:
            pass
            hconn = http.client.HTTPConnection('qdb.pesterchum.net', 80,
                                           timeout=15)
            hconn.request("POST", "/index.php", params, headers)
            response = hconn.getresponse()
            if response.status == 200:
                self.sending.sendinglabel.setText("SUCC3SS!")
            else:
                self.sending.sendinglabel.setText("F41L3D: %s %s" % (response.status, response.reason))
            hconn.close()
        except Exception as e:
            self.sending.sendinglabel.setText("F41L3D: %s" % (e))
        del self.sending
dataobjs.py
class PesterHistory(object):
    def __init__(self):
        self.history = []
        self.current = 0
        self.saved = None
    def next(self, text):
        if self.current == 0:
            return None
        if self.current == len(self.history):
            self.save(text)
        self.current -= 1
        text = self.history[self.current]
        return text
    def prev(self):
        self.current += 1
        if self.current >= len(self.history):
            self.current = len(self.history)
            return self.retrieve()
        return self.history[self.current]
    def reset(self):
        self.current = len(self.history)
        self.saved = None
    def save(self, text):
        self.saved = text
    def retrieve(self):
        return self.saved
    def add(self, text):
        if len(self.history) == 0 or text != self.history[len(self.history)-1]:
            self.history.append(text)
        self.reset()
dataobjs.py
class pesterQuirk(object):
    def __init__(self, quirk):
        if type(quirk) != dict:
            raise ValueError("Quirks must be given a dictionary")
        self.quirk = quirk
        self.type = self.quirk["type"]
        if "on" not in self.quirk:
            self.quirk["on"] = True
        self.on = self.quirk["on"]
        if "group" not in self.quirk:
            self.quirk["group"] = "Miscellaneous"
        self.group = self.quirk["group"]
    def apply(self, string, first=False, last=False):
        if not self.on:
            return string
        elif self.type == "prefix":
            return self.quirk["value"] + string
        elif self.type == "suffix":
            return string + self.quirk["value"]
        elif self.type == "replace":
            return string.replace(self.quirk["from"], self.quirk["to"])
        elif self.type == "regexp":
            fr = self.quirk["from"]
            if not first and len(fr) > 0 and fr[0] == "^":
                return string
            if not last and len(fr) > 0 and fr[len(fr)-1] == "$":
                return string
            to = self.quirk["to"]
            pt = parseRegexpFunctions(to)
            return re.sub(fr, pt.expand, string)
        elif self.type == "random":
            if len(self.quirk["randomlist"]) == 0:
                return string
            fr = self.quirk["from"]
            if not first and len(fr) > 0 and fr[0] == "^":
                return string
            if not last and len(fr) > 0 and fr[len(fr)-1] == "$":
                return string
            def randomrep(mo):
                choice = random.choice(self.quirk["randomlist"])
                pt = parseRegexpFunctions(choice)
                return pt.expand(mo)
            return re.sub(self.quirk["from"], randomrep, string)
        elif self.type == "spelling":
            percentage = self.quirk["percentage"]/100.0
            words = string.split(" ")
            newl = []
            ctag = re.compile("(</?c=?.*?>)", re.I)
            for w in words:
                p = random.random()
                if not ctag.search(w) and p < percentage:
                    newl.append(mispeller(w))
                elif p < percentage:
                    split = ctag.split(w)
                    tmp = []
                    for s in split:
                        if s and not ctag.search(s):
                            tmp.append(mispeller(s))
                        else:
                            tmp.append(s)
                    newl.append("".join(tmp))
                else:
                    newl.append(w)
            return " ".join(newl)

    def __str__(self):
        if self.type == "prefix":
            return "BEGIN WITH: %s" % (self.quirk["value"])
        elif self.type == "suffix":
            return "END WITH: %s" % (self.quirk["value"])
        elif self.type == "replace":
            return "REPLACE %s WITH %s" % (self.quirk["from"], self.quirk["to"])
        elif self.type == "regexp":
            return "REGEXP: %s REPLACED WITH %s" % (self.quirk["from"], self.quirk["to"])
        elif self.type == "random":
            return "REGEXP: %s RANDOMLY REPLACED WITH %s" % (self.quirk["from"], [r for r in self.quirk["randomlist"]])
        elif self.type == "spelling":
            return "MISPELLER: %d%%" % (self.quirk["percentage"])
generic.py
class MultiTextDialog(QtWidgets.QDialog):
    def __init__(self, title, parent, *queries):
        QtWidgets.QDialog.__init__(self, parent)
        self.setWindowTitle(title)
        if len(queries) == 0:
            return
        self.inputs = {}
        layout_1 = QtWidgets.QHBoxLayout()
        for d in queries:
            label = d["label"]
            inputname = d["inputname"]
            value = d.get("value", "")
            l = QtWidgets.QLabel(label, self)
            layout_1.addWidget(l)
            self.inputs[inputname] = QtWidgets.QLineEdit(value, self)
            layout_1.addWidget(self.inputs[inputname])
        self.ok = QtWidgets.QPushButton("OK", self, clicked=self.accept)
        self.ok.setDefault(True)
        self.cancel = QtWidgets.QPushButton("CANCEL", self, clicked=self.reject)
        layout_ok = QtWidgets.QHBoxLayout()
        layout_ok.addWidget(self.cancel)
        layout_ok.addWidget(self.ok)

        layout_0 = QtWidgets.QVBoxLayout()
        layout_0.addLayout(layout_1)
        layout_0.addLayout(layout_ok)

        self.setLayout(layout_0)
    def getText(self):
        r = self.exec_()
        if r == QtWidgets.QDialog.Accepted:
            retval = {}
            for (name, widget) in self.inputs.items():
                retval[name] = str(widget.text())
            return retval
        else:
            return None
convo.py
class PesterInput(QtWidgets.QLineEdit):
    def __init__(self, theme, parent=None):
        QtWidgets.QLineEdit.__init__(self, parent)
        self.setStyleSheet(theme["convo/input/style"])
    def changeTheme(self, theme):
        self.setStyleSheet(theme["convo/input/style"])
    def focusInEvent(self, event):
        self.parent().clearNewMessage()
        self.parent().textArea.textCursor().clearSelection()
        QtWidgets.QLineEdit.focusInEvent(self, event)
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            text = str(self.text())
            next = self.parent().history.next(text)
            if next is not None:
                self.setText(next)
        elif event.key() == QtCore.Qt.Key_Down:
            prev = self.parent().history.prev()
            if prev is not None:
                self.setText(prev)
        elif event.key() in [QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]:
            self.parent().textArea.keyPressEvent(event)
        self.parent().mainwindow.idletime = 0
        QtWidgets.QLineEdit.keyPressEvent(self, event)
generic.py
class RightClickList(QtWidgets.QListWidget):
    def contextMenuEvent(self, event):
        #fuckin Qt
        if event.reason() == QtGui.QContextMenuEvent.Mouse:
            listing = self.itemAt(event.pos())
            self.setCurrentItem(listing)
            optionsMenu = self.getOptionsMenu()
            if optionsMenu:
                optionsMenu.popup(event.globalPos())
    def getOptionsMenu(self):
        return self.optionsMenu
dataobjs.py
class PesterProfile(object):
    def __init__(self, handle, color=None, mood=Mood("offline"), group=None, notes="", chumdb=None):
        self.handle = handle
        if color is None:
            if chumdb:
                color = chumdb.getColor(handle, QtGui.QColor("black"))
            else:
                color = QtGui.QColor("black")
        self.color = color
        self.mood = mood
        if group is None:
            if chumdb:
                group = chumdb.getGroup(handle, "Chums")
            else:
                group = "Chums"
        self.group = group
        self.notes = notes
    def initials(self, time=None):
        handle = self.handle
        caps = [l for l in handle if l.isupper()]
        if not caps:
            caps = [""]
        initials = (handle[0]+caps[0]).upper()
        if hasattr(self, 'time') and time:
            if self.time > time:
                return "F"+initials
            elif self.time < time:
                return "P"+initials
            else:
                return "C"+initials
        else:
            return (handle[0]+caps[0]).upper()
    def colorhtml(self):
        if self.color:
            return self.color.name()
        else:
            return "#000000"
    def colorcmd(self):
        if self.color:
            (r, g, b, a) = self.color.getRgb()
            return "%d,%d,%d" % (r,g,b)
        else:
            return "0,0,0"
    def plaindict(self):
        return (self.handle, {"handle": self.handle,
                              "mood": self.mood.name(),
                              "color": str(self.color.name()),
                              "group": str(self.group),
                              "notes": str(self.notes)})
    def blocked(self, config):
        return self.handle in config.getBlocklist()

    def memsg(self, syscolor, lexmsg, time=None):
        suffix = lexmsg[0].suffix
        msg = convertTags(lexmsg[1:], "text")
        uppersuffix = suffix.upper()
        if time is not None:
            handle = "%s %s" % (time.temporal, self.handle)
            initials = time.pcf+self.initials()+time.number+uppersuffix
        else:
            handle = self.handle
            initials = self.initials()+uppersuffix
        return "<c=%s>-- %s%s <c=%s>[%s]</c> %s --</c>" % (syscolor.name(), handle, suffix, self.colorhtml(), initials, msg)
    def pestermsg(self, otherchum, syscolor, verb):
        return "<c=%s>-- %s <c=%s>[%s]</c> %s %s <c=%s>[%s]</c> at %s --</c>" % (syscolor.name(), self.handle, self.colorhtml(), self.initials(), verb, otherchum.handle, otherchum.colorhtml(), otherchum.initials(), datetime.now().strftime("%H:%M"))
    def moodmsg(self, mood, syscolor, theme):
        return "<c=%s>-- %s <c=%s>[%s]</c> changed their mood to %s <img src='%s' /> --</c>" % (syscolor.name(), self.handle, self.colorhtml(), self.initials(), mood.name().upper(), theme["main/chums/moods"][mood.name()]["icon"])
    def idlemsg(self, syscolor, verb):
        return "<c=%s>-- %s <c=%s>[%s]</c> %s --</c>" % (syscolor.name(), self.handle, self.colorhtml(), self.initials(), verb)
    def memoclosemsg(self, syscolor, initials, verb):
        if type(initials) == type(list()):
            return "<c=%s><c=%s>%s</c> %s.</c>" % (syscolor.name(), self.colorhtml(), ", ".join(initials), verb)
        else:
            return "<c=%s><c=%s>%s%s%s</c> %s.</c>" % (syscolor.name(), self.colorhtml(), initials.pcf, self.initials(), initials.number, verb)
    def memonetsplitmsg(self, syscolor, initials):
        if len(initials) <= 0:
            return "<c=%s>Netsplit quits: <c=black>None</c></c>" % (syscolor.name())
        else:
            return "<c=%s>Netsplit quits: <c=black>%s</c></c>" % (syscolor.name(), ", ".join(initials))
    def memoopenmsg(self, syscolor, td, timeGrammar, verb, channel):
        (temporal, pcf, when) = (timeGrammar.temporal, timeGrammar.pcf, timeGrammar.when)
        timetext = timeDifference(td)
        initials = pcf+self.initials()
        return "<c=%s><c=%s>%s</c> %s %s %s.</c>" % \
            (syscolor.name(), self.colorhtml(), initials, timetext, verb, channel[1:].upper().replace("_", " "))
    def memobanmsg(self, opchum, opgrammar, syscolor, initials, reason):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        if type(initials) == type(list()):
            if opchum.handle == reason:
                return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo." % \
                    (opchum.colorhtml(), opinit, self.colorhtml(), ", ".join(initials))
            else:
                return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>." % \
                    (opchum.colorhtml(), opinit, self.colorhtml(), ", ".join(initials), str(reason))
        else:
            initials = timeGrammar.pcf+self.initials()+timeGrammar.number
            if opchum.handle == reason:
                return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo." % \
                    (opchum.colorhtml(), opinit, self.colorhtml(), initials)
            else:
                return "<c=%s>%s</c> banned <c=%s>%s</c> from responding to memo: <c=black>[%s]</c>." % \
                    (opchum.colorhtml(), opinit, self.colorhtml(), initials, str(reason))
    def memopermabanmsg(self, opchum, opgrammar, syscolor, timeGrammar):
        initials = timeGrammar.pcf+self.initials()+timeGrammar.number
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        return "<c=%s>%s</c> permabanned <c=%s>%s</c> from the memo." % \
            (opchum.colorhtml(), opinit, self.colorhtml(), initials)
    def memojoinmsg(self, syscolor, td, timeGrammar, verb):
        (temporal, pcf, when) = (timeGrammar.temporal, timeGrammar.pcf, timeGrammar.when)
        timetext = timeDifference(td)
        initials = pcf+self.initials()+timeGrammar.number
        return "<c=%s><c=%s>%s %s [%s]</c> %s %s.</c>" % \
            (syscolor.name(), self.colorhtml(), temporal, self.handle,
             initials, timetext, verb)
    def memoopmsg(self, opchum, opgrammar, syscolor):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        return "<c=%s>%s</c> made <c=%s>%s</c> an OP." % \
            (opchum.colorhtml(), opinit, self.colorhtml(), self.initials())
    def memodeopmsg(self, opchum, opgrammar, syscolor):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        return "<c=%s>%s</c> took away <c=%s>%s</c>'s OP powers." % \
            (opchum.colorhtml(), opinit, self.colorhtml(), self.initials())
    def memovoicemsg(self, opchum, opgrammar, syscolor):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        return "<c=%s>%s</c> gave <c=%s>%s</c> voice." % \
            (opchum.colorhtml(), opinit, self.colorhtml(), self.initials())
    def memodevoicemsg(self, opchum, opgrammar, syscolor):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        return "<c=%s>%s</c> took away <c=%s>%s</c>'s voice." % \
            (opchum.colorhtml(), opinit, self.colorhtml(), self.initials())
    def memomodemsg(self, opchum, opgrammar, syscolor, modeverb, modeon):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        if modeon: modeon = "now"
        else:      modeon = "no longer"
        return "<c=%s>Memo is %s <c=black>%s</c> by <c=%s>%s</c></c>" % \
            (syscolor.name(), modeon, modeverb, opchum.colorhtml(), opinit)
    def memoquirkkillmsg(self, opchum, opgrammar, syscolor):
        opinit = opgrammar.pcf+opchum.initials()+opgrammar.number
        return "<c=%s><c=%s>%s</c> turned off your quirk.</c>" % \
            (syscolor.name(), opchum.colorhtml(), opinit)

    @staticmethod
    def checkLength(handle):
        return len(handle) <= 256
    @staticmethod
    def checkValid(handle):
        caps = [l for l in handle if l.isupper()]
        if len(caps) != 1:
            return (False, "Must have exactly 1 uppercase letter")
        if handle[0].isupper():
            return (False, "Cannot start with uppercase letter")
        if re.search("[^A-Za-z0-9]", handle) is not None:
            return (False, "Only alphanumeric characters allowed")
        return (True,)


Question: from PyQt5 import QtGui, QtCore, QtWidgets
from os import remove
from generic import RightClickList, RightClickTree, MultiTextDialog
from dataobjs import pesterQuirk, PesterProfile
from memos import TimeSlider, TimeInput
from version import _pcVersion
from copy import copy
from convo import PesterInput, PesterText
from parsetools import convertTags, lexMessage, splitMessage, mecmd, colorBegin, colorEnd, img2smiley, smiledict
from dataobjs import pesterQuirks, PesterHistory
        from parsetools import quirkloader
        from parsetools import reloadQuirkFunctions, quirkloader
import re, ostools
        i = self.currentItem()
        found = self.findItems(i.text(0), QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive)
        for f in found:
            if not f.isSelected(): continue
            if not f.parent(): # group
                msgbox = QtWidgets.QMessageBox()
                msgbox.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
                msgbox.setWindowTitle("WARNING!")
                msgbox.setInformativeText("Are you sure you want to delete the quirk group: %s" % (f.text(0)))
                msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
                ret = msgbox.exec_()
                if ret == QtWidgets.QMessageBox.Ok:
                    self.takeTopLevelItem(self.indexOfTopLevelItem(f))
            else:
                f.parent().takeChild(f.parent().indexOfChild(f))
        self.changeCheckState()

    @QtCore.pyqtSlot()
    def addQuirkGroup(self):
        if not hasattr(self, 'addgroupdialog'):
            self.addgroupdialog = None
        if not self.addgroupdialog:
            (gname, ok) = QtWidgets.QInputDialog.getText(self, "Add Group", "Enter a name for the new quirk group:")
            if ok:
                gname = str(gname)
                if re.search("[^A-Za-z0-9_\s]", gname) is not None:
                    msgbox = QtWidgets.QMessageBox()
                    msgbox.setInformativeText("THIS IS NOT A VALID GROUP NAME")
                    msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                    ret = msgbox.exec_()
                    self.addgroupdialog = None
                    return
                found = self.findItems(gname, QtCore.Qt.MatchExactly)
                if found:
                    msgbox = QtWidgets.QMessageBox()
                    msgbox.setInformativeText("THIS QUIRK GROUP ALREADY EXISTS")
                    msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok)
                    ret = msgbox.exec_()
                    return
                child_1 = QtWidgets.QTreeWidgetItem([gname])
                self.addTopLevelItem(child_1)
                child_1.setFlags(child_1.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
                child_1.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.DontShowIndicatorWhenChildless)
                child_1.setCheckState(0,0)
                child_1.setExpanded(True)

            self.addgroupdialog = None

    @QtCore.pyqtSlot()
    def changeCheckState(self):
        index = self.indexOfTopLevelItem(self.currentItem())
        if index == -1:
            for i in range(self.topLevelItemCount()):
                allChecked = True
                noneChecked = True
                for j in range(self.topLevelItem(i).childCount()):
                    if self.topLevelItem(i).child(j).checkState(0):
                        noneChecked = False
                    else:
                        allChecked = False
                if allChecked:    self.topLevelItem(i).setCheckState(0, 2)
                elif noneChecked: self.topLevelItem(i).setCheckState(0, 0)
                else:             self.topLevelItem(i).setCheckState(0, 1)
        else:
            state = self.topLevelItem(index).checkState(0)
            for j in range(self.topLevelItem(index).childCount()):
                self.topLevelItem(index).child(j).setCheckState(0, state)

class QuirkTesterWindow(QtWidgets.QDialog):
    def __init__(self, parent):
        QtWidgets.QDialog.__init__(self, parent)
        self.prnt = parent
        self.mainwindow = parent.mainwindow
        self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
        self.setWindowTitle("Quirk Tester")
        self.resize(350,300)

        self.textArea = PesterText(self.mainwindow.theme, self)
        self.textInput = PesterInput(self.mainwindow.theme, self)
        self.textInput.setFocus()

        self.textInput.returnPressed.connect(self.sentMessage)

        self.chumopen = True
        self.chum = self.mainwindow.profile()
        self.history = PesterHistory()

        layout_0 = QtWidgets.QVBoxLayout()
        layout_0.addWidget(self.textArea)
        layout_0.addWidget(self.textInput)
        self.setLayout(layout_0)

    def parent(self):
        return self.prnt

    def clearNewMessage(self):
        pass
    @QtCore.pyqtSlot()
    def sentMessage(self):
        text = str(self.textInput.text())
        if text == "" or text[0:11] == "PESTERCHUM:":
            return
        self.history.add(text)
        quirks = pesterQuirks(self.parent().testquirks())
        lexmsg = lexMessage(text)
        if type(lexmsg[0]) is not mecmd:
            try:
                lexmsg = quirks.apply(lexmsg)
            except Exception as e:
                msgbox = QtWidgets.QMessageBox()
                msgbox.setText("Whoa there! There seems to be a problem.")
                msgbox.setInformativeText("A quirk seems to be having a problem. (Possibly you're trying to capture a non-existant group?)\n\
                %s" % e)
                msgbox.exec_()
                return
        lexmsgs = splitMessage(lexmsg)

        for lm in lexmsgs:
            serverMsg = copy(lm)
            self.addMessage(lm, True)

Answer:             text = convertTags(serverMsg, "ctag")