Merge pull request #120 from Dpeta/irc-mode2

- Adds an "IRC compatibility mode" toggle in settings, which stops the clients from sending any Pesterchum-unique syntax/commands. This replaces low latency mode as they both result in leaving the #pesterchum channel.
- Adds a toggle to force valid initials on all incoming messages, this breaks doc scratch roleplayer text without initials if enabled.
- Renames the 'Connection' tab in options to 'IRC'.

Enabling both of these should make chatting on a normal IRC server with Pesterchum manageable.
This commit is contained in:
Dpeta 2023-02-16 23:55:16 +01:00 committed by GitHub
commit 7172db6151
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 154 additions and 65 deletions

View file

@ -898,7 +898,7 @@ class PesterConvo(QtWidgets.QFrame):
self.chum.color = color self.chum.color = color
def addMessage(self, msg, me=True): def addMessage(self, msg, me=True):
if type(msg) in [str, str]: if isinstance(msg, str):
lexmsg = lexMessage(msg) lexmsg = lexMessage(msg)
else: else:
lexmsg = msg lexmsg = msg
@ -1096,7 +1096,12 @@ class PesterConvo(QtWidgets.QFrame):
text = self.textInput.text() text = self.textInput.text()
text = str(self.textInput.text()) text = str(self.textInput.text())
return parsetools.kxhandleInput(self, text, flavor="convo") return parsetools.kxhandleInput(
self,
text,
flavor="convo",
irc_compatible=self.mainwindow.config.irc_compatibility_mode(),
)
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def addThisChum(self): def addThisChum(self):

View file

@ -321,7 +321,7 @@ class PesterProfile:
def colorcmd(self): def colorcmd(self):
if self.color: if self.color:
(r, g, b, _) = self.color.getRgb() (r, g, b, _a) = self.color.getRgb()
return "%d,%d,%d" % (r, g, b) return "%d,%d,%d" % (r, g, b)
else: else:
return "0,0,0" return "0,0,0"

43
irc.py
View file

@ -829,25 +829,26 @@ class PesterIRC(QtCore.QThread):
) )
self.connected.emit() # Alert main thread that we've connected. self.connected.emit() # Alert main thread that we've connected.
profile = self.mainwindow.profile() profile = self.mainwindow.profile()
if not self.mainwindow.config.lowBandwidth(): if self.mainwindow.config.irc_compatibility_mode():
# Negotiate capabilities return
self._send_irc.cap("REQ", "message-tags") # Negotiate capabilities
self._send_irc.cap( self._send_irc.cap("REQ", "message-tags")
"REQ", "draft/metadata-notify-2" self._send_irc.cap(
) # <--- Not required in the unreal5 module implementation "REQ", "draft/metadata-notify-2"
self._send_irc.cap("REQ", "pesterchum-tag") # <--- Currently not using this ) # <--- Not required in the unreal5 module implementation
self._send_irc.cap("REQ", "twitch.tv/membership") # Twitch silly self._send_irc.cap("REQ", "pesterchum-tag") # <--- Currently not using this
self._send_irc.join("#pesterchum") self._send_irc.cap("REQ", "twitch.tv/membership") # Twitch silly
# Get mood self._send_irc.join("#pesterchum")
mood = profile.mood.value_str() # Get mood
# Moods via metadata mood = profile.mood.value_str()
self._send_irc.metadata("*", "sub", "mood") # Moods via metadata
self._send_irc.metadata("*", "set", "mood", mood) self._send_irc.metadata("*", "sub", "mood")
# Color via metadata self._send_irc.metadata("*", "set", "mood", mood)
self._send_irc.metadata("*", "sub", "color") # Color via metadata
self._send_irc.metadata("*", "set", "color", profile.color.name()) self._send_irc.metadata("*", "sub", "color")
# Backwards compatible moods self._send_irc.metadata("*", "set", "color", profile.color.name())
self._send_irc.privmsg("#pesterchum", f"MOOD >{mood}") # Backwards compatible moods
self._send_irc.privmsg("#pesterchum", f"MOOD >{mood}")
def _featurelist(self, _target, _handle, *params): def _featurelist(self, _target, _handle, *params):
"""Numerical reply 005 RPL_ISUPPORT to communicate supported server features. """Numerical reply 005 RPL_ISUPPORT to communicate supported server features.
@ -857,8 +858,8 @@ class PesterIRC(QtCore.QThread):
""" """
features = params[:-1] features = params[:-1]
PchumLog.info("Server _featurelist: %s", features) PchumLog.info("Server _featurelist: %s", features)
for feature in features: if not self.metadata_supported:
if feature.casefold().startswith("metadata"): if any(feature.startswith("METADATA") for feature in features):
PchumLog.info("Server supports metadata.") PchumLog.info("Server supports metadata.")
self.metadata_supported = True self.metadata_supported = True

View file

@ -20,13 +20,16 @@ from parsetools import (
timeProtocol, timeProtocol,
lexMessage, lexMessage,
colorBegin, colorBegin,
addTimeInitial,
mecmd, mecmd,
smiledict, smiledict,
) )
from logviewer import PesterLogViewer from logviewer import PesterLogViewer
PchumLog = logging.getLogger("pchumLogger") PchumLog = logging.getLogger("pchumLogger")
_valid_memo_msg_start = re.compile(
r"^<c=((\d+,\d+,\d+)|(#([a-fA-F0-9]{6})|(#[a-fA-F0-9]{3})))>([A-Z]{3}):\s"
)
# Python 3 # Python 3
QString = str QString = str
@ -356,14 +359,43 @@ class MemoText(PesterText):
except: except:
pass pass
def make_valid(self, msg, chum, parent, window, me):
"""Adds initials and color to a message if they're missing."""
initials = chum.initials()
match = re.match(_valid_memo_msg_start, msg)
detected_initials = None
if match:
try:
# Get initials used in msg, check if valid later
detected_initials = match.group(6)[1:]
except IndexError:
pass # IndexError is fine, just means the initials are invalid
if not match or detected_initials != initials:
if chum is me:
color = me.colorcmd()
msg = f"<c={color}>{initials}: {msg}</c>"
msg = addTimeInitial(msg, parent.time.getGrammar())
else:
color = window.chumdb.getColor(chum.handle)
if color:
(r, g, b, _a) = color.getRgb()
color = f"{r},{g},{b}"
else:
color = "0,0,0"
msg = f"<c={color}>{initials}: {msg}</c>"
msg = addTimeInitial(msg, parent.times[chum.handle].getGrammar())
return msg
def addMessage(self, msg, chum): def addMessage(self, msg, chum):
if type(msg) in [str, str]:
lexmsg = lexMessage(msg)
else:
lexmsg = msg
parent = self.parent() parent = self.parent()
window = parent.mainwindow window = parent.mainwindow
me = window.profile() me = window.profile()
if isinstance(msg, str):
if self.mainwindow.config.force_prefix():
msg = self.make_valid(msg, chum, parent, window, me)
lexmsg = lexMessage(msg)
else:
lexmsg = msg
if self.mainwindow.config.animations(): if self.mainwindow.config.animations():
for m in self.urls: for m in self.urls:
if convertTags(lexmsg).find(self.urls[m].toString()) != -1: if convertTags(lexmsg).find(self.urls[m].toString()) != -1:
@ -698,6 +730,8 @@ class PesterMemo(PesterConvo):
return PesterIcon(self.mainwindow.theme["memos/memoicon"]) return PesterIcon(self.mainwindow.theme["memos/memoicon"])
def sendTimeInfo(self, newChum=False): def sendTimeInfo(self, newChum=False):
if self.mainwindow.config.irc_compatibility_mode():
return
if newChum: if newChum:
self.messageSent.emit( self.messageSent.emit(
"PESTERCHUM:TIME>%s" % (delta2txt(self.time.getTime(), "server") + "i"), "PESTERCHUM:TIME>%s" % (delta2txt(self.time.getTime(), "server") + "i"),
@ -1398,7 +1432,12 @@ class PesterMemo(PesterConvo):
def sentMessage(self): def sentMessage(self):
text = str(self.textInput.text()) text = str(self.textInput.text())
return parsetools.kxhandleInput(self, text, flavor="memos") return parsetools.kxhandleInput(
self,
text,
flavor="memos",
irc_compatible=self.mainwindow.config.irc_compatibility_mode(),
)
@QtCore.pyqtSlot(QString) @QtCore.pyqtSlot(QString)
def namesUpdated(self, channel): def namesUpdated(self, channel):
@ -1730,7 +1769,7 @@ class PesterMemo(PesterConvo):
txt_time = delta2txt(time, "server") txt_time = delta2txt(time, "server")
# Only send if time isn't CURRENT, it's very spammy otherwise. # Only send if time isn't CURRENT, it's very spammy otherwise.
# CURRENT should be the default already. # CURRENT should be the default already.
if txt_time != "i": if txt_time != "i" and not self.mainwindow.config.irc_compatibility_mode():
serverText = "PESTERCHUM:TIME>" + txt_time serverText = "PESTERCHUM:TIME>" + txt_time
self.messageSent.emit(serverText, self.title()) self.messageSent.emit(serverText, self.title())
elif update == "+q": elif update == "+q":
@ -1951,6 +1990,8 @@ class PesterMemo(PesterConvo):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def sendtime(self): def sendtime(self):
if self.mainwindow.config.irc_compatibility_mode():
return
# me = self.mainwindow.profile() # me = self.mainwindow.profile()
# systemColor = QtGui.QColor(self.mainwindow.theme["memos/systemMsgColor"]) # systemColor = QtGui.QColor(self.mainwindow.theme["memos/systemMsgColor"])
time = txt2delta(self.timeinput.text()) time = txt2delta(self.timeinput.text())

View file

@ -323,10 +323,15 @@ class QuirkTesterWindow(QtWidgets.QDialog):
def sentMessage(self): def sentMessage(self):
text = str(self.textInput.text()) text = str(self.textInput.text())
return parsetools.kxhandleInput(self, text, "menus") return parsetools.kxhandleInput(
self,
text,
"menus",
irc_compatible=self.mainwindow.config.irc_compatibility_mode(),
)
def addMessage(self, msg, me=True): def addMessage(self, msg, me=True):
if type(msg) in [str, str]: if isinstance(msg, str):
lexmsg = lexMessage(msg) lexmsg = lexMessage(msg)
else: else:
lexmsg = msg lexmsg = msg
@ -1221,7 +1226,7 @@ class PesterOptions(QtWidgets.QDialog):
"Logging", "Logging",
"Idle/Updates", "Idle/Updates",
"Theme", "Theme",
"Connection", "IRC",
] ]
if parent.advanced: if parent.advanced:
self.tabNames.append("Advanced") self.tabNames.append("Advanced")
@ -1233,17 +1238,34 @@ class PesterOptions(QtWidgets.QDialog):
self.tabs.button(-2).setChecked(True) self.tabs.button(-2).setChecked(True)
self.pages = QtWidgets.QStackedWidget(self) self.pages = QtWidgets.QStackedWidget(self)
self.bandwidthcheck = QtWidgets.QCheckBox("Low Bandwidth", self) self.irc_mode_check = QtWidgets.QCheckBox("IRC compatibility mode", self)
if self.config.lowBandwidth(): if self.config.irc_compatibility_mode():
self.bandwidthcheck.setChecked(True) self.irc_mode_check.setChecked(True)
bandwidthLabel = QtWidgets.QLabel( bandwidthLabel = QtWidgets.QLabel(
"(Stops you for receiving the flood of MOODS,\n" "Enable this if you're planning on using Pesterchum on a server with normal IRC clients."
" though stops chumlist from working properly)" "\nStops the client from sending or requesting:"
"\n - Non-metadata moods (MOOD >0, GETMOOD, etc.)"
"\n - Non-metadata dm colors (COLOR >0,0,0)"
"\n - Memo message initials and color (<c=0,0,0>EB: </c>)"
"\n - Memo timelines"
"\n - Misc. PESTERCHUM:X commands (BEGIN, CEASE, BLOCK, IDLE, etc.)"
) )
font = bandwidthLabel.font() font = bandwidthLabel.font()
font.setPointSize(8) font.setPointSize(8)
bandwidthLabel.setFont(font) bandwidthLabel.setFont(font)
self.force_prefix_check = QtWidgets.QCheckBox(
"Force all memo messages to have valid initials.", self
)
if self.config.force_prefix():
self.force_prefix_check.setChecked(True)
initials_label = QtWidgets.QLabel(
"Disable to allow users to send messages without initials, like Doc Scratch."
)
font = initials_label.font()
font.setPointSize(8)
initials_label.setFont(font)
self.autonickserv = QtWidgets.QCheckBox("Auto-Identify with NickServ", self) self.autonickserv = QtWidgets.QCheckBox("Auto-Identify with NickServ", self)
self.autonickserv.setChecked(parent.userprofile.getAutoIdentify()) self.autonickserv.setChecked(parent.userprofile.getAutoIdentify())
self.autonickserv.stateChanged[int].connect(self.autoNickServChange) self.autonickserv.stateChanged[int].connect(self.autoNickServChange)
@ -1617,8 +1639,10 @@ class PesterOptions(QtWidgets.QDialog):
widget = QtWidgets.QWidget() widget = QtWidgets.QWidget()
layout_connect = QtWidgets.QVBoxLayout(widget) layout_connect = QtWidgets.QVBoxLayout(widget)
layout_connect.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop) layout_connect.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
layout_connect.addWidget(self.bandwidthcheck) layout_connect.addWidget(self.irc_mode_check)
layout_connect.addWidget(bandwidthLabel) layout_connect.addWidget(bandwidthLabel)
layout_connect.addWidget(self.force_prefix_check)
layout_connect.addWidget(initials_label)
layout_connect.addWidget(self.autonickserv) layout_connect.addWidget(self.autonickserv)
layout_indent = QtWidgets.QVBoxLayout() layout_indent = QtWidgets.QVBoxLayout()
layout_indent.addWidget(self.nickservpass) layout_indent.addWidget(self.nickservpass)

View file

@ -284,7 +284,7 @@ def kxlexMsg(string):
return msg return msg
def lexMessage(string): def lexMessage(string: str):
lexlist = [ lexlist = [
(mecmd, _mecmdre), (mecmd, _mecmdre),
(colorBegin, _ctag_begin), (colorBegin, _ctag_begin),
@ -302,10 +302,12 @@ def lexMessage(string):
(honker, _honk), (honker, _honk),
] ]
string = str(string)
string = string.replace("\n", " ").replace("\r", " ") string = string.replace("\n", " ").replace("\r", " ")
lexed = lexer(str(string), lexlist) lexed = lexer(string, lexlist)
return balance(lexed)
def balance(lexed):
balanced = [] balanced = []
beginc = 0 beginc = 0
endc = 0 endc = 0
@ -683,7 +685,7 @@ def _is_ooc(msg, strict=True):
return False return False
def kxhandleInput(ctx, text=None, flavor=None): def kxhandleInput(ctx, text=None, flavor=None, irc_compatible=False):
"""The function that user input that should be sent to the server is routed """The function that user input that should be sent to the server is routed
through. Handles lexing, splitting, and quirk application, as well as through. Handles lexing, splitting, and quirk application, as well as
sending.""" sending."""
@ -699,11 +701,10 @@ def kxhandleInput(ctx, text=None, flavor=None):
if text is None: if text is None:
# Fetch the raw text from the input box. # Fetch the raw text from the input box.
text = ctx.textInput.text() text = ctx.textInput.text()
text = str(ctx.textInput.text())
# Preprocessing stuff. # Preprocessing stuff.
msg = text.strip() msg = text.strip()
if msg == "" or msg.startswith("PESTERCHUM:"): if not msg or msg.startswith("PESTERCHUM:"):
# We don't allow users to send system messages. There's also no # We don't allow users to send system messages. There's also no
# point if they haven't entered anything. # point if they haven't entered anything.
return return
@ -827,7 +828,8 @@ def kxhandleInput(ctx, text=None, flavor=None):
if flavor == "convo": if flavor == "convo":
# if ceased, rebegin # if ceased, rebegin
if hasattr(ctx, "chumopen") and not ctx.chumopen: if hasattr(ctx, "chumopen") and not ctx.chumopen:
ctx.mainwindow.newConvoStarted.emit(QString(ctx.title()), True) if not irc_compatible:
ctx.mainwindow.newConvoStarted.emit(QString(ctx.title()), True)
ctx.setChumOpen(True) ctx.setChumOpen(True)
# Post-process and send the messages. # Post-process and send the messages.
@ -845,8 +847,13 @@ def kxhandleInput(ctx, text=None, flavor=None):
clientMsg = copy(lm) clientMsg = copy(lm)
serverMsg = copy(lm) serverMsg = copy(lm)
# If in IRC-compatible mode, remove color tags.
if irc_compatible:
serverMsg = re.sub(_ctag_begin, "", serverMsg)
serverMsg = re.sub(_ctag_end, "", serverMsg)
# Memo-specific processing. # Memo-specific processing.
if flavor == "memos" and not is_action: if flavor == "memos" and not is_action and not irc_compatible:
# Quirks were already applied, so get the prefix/postfix stuff # Quirks were already applied, so get the prefix/postfix stuff
# ready. # ready.
# We fetched the information outside of the loop, so just # We fetched the information outside of the loop, so just

View file

@ -1899,7 +1899,8 @@ class PesterWindow(MovingWindow):
def newMessage(self, handle, msg): def newMessage(self, handle, msg):
if handle in self.config.getBlocklist(): if handle in self.config.getBlocklist():
# yeah suck on this # yeah suck on this
self.sendMessage.emit("PESTERCHUM:BLOCKED", handle) if not self.config.irc_compatibility_mode():
self.sendMessage.emit("PESTERCHUM:BLOCKED", handle)
return return
# notify # notify
if self.config.notifyOptions() & self.config.NEWMSG: if self.config.notifyOptions() & self.config.NEWMSG:
@ -1951,7 +1952,6 @@ class PesterWindow(MovingWindow):
# TODO: This is really bad practice. Fix it later. # TODO: This is really bad practice. Fix it later.
return return
memo = self.memos[chan] memo = self.memos[chan]
msg = str(msg)
if handle not in memo.times: if handle not in memo.times:
# new chum! time current # new chum! time current
newtime = datetime.timedelta(0) newtime = datetime.timedelta(0)
@ -2007,7 +2007,7 @@ class PesterWindow(MovingWindow):
self.trollslum.updateMood(handle, mood) self.trollslum.updateMood(handle, mood)
def newConversation(self, chum, initiated=True): def newConversation(self, chum, initiated=True):
if type(chum) in [str, str]: if isinstance(chum, str):
matchingChums = [c for c in self.chumList.chums if c.handle == chum] matchingChums = [c for c in self.chumList.chums if c.handle == chum]
if len(matchingChums) > 0: if len(matchingChums) > 0:
mood = matchingChums[0].mood mood = matchingChums[0].mood
@ -2032,7 +2032,7 @@ class PesterWindow(MovingWindow):
) )
convoWindow.windowClosed["QString"].connect(self.closeConvo) convoWindow.windowClosed["QString"].connect(self.closeConvo)
self.convos[chum.handle] = convoWindow self.convos[chum.handle] = convoWindow
if str(chum.handle).upper() in BOTNAMES: if chum.handle.upper() in BOTNAMES or self.config.irc_compatibility_mode():
convoWindow.toggleQuirks(True) convoWindow.toggleQuirks(True)
convoWindow.quirksOff.setChecked(True) convoWindow.quirksOff.setChecked(True)
if str(chum.handle).upper() in CUSTOMBOTS: if str(chum.handle).upper() in CUSTOMBOTS:
@ -2616,7 +2616,8 @@ class PesterWindow(MovingWindow):
self.theme["convo/text/ceasepester"], self.theme["convo/text/ceasepester"],
), ),
) )
self.convoClosed.emit(handle) if not self.config.irc_compatibility_mode():
self.convoClosed.emit(handle)
self.chatlog.finish(h) self.chatlog.finish(h)
del self.convos[h] del self.convos[h]
@ -2881,7 +2882,8 @@ class PesterWindow(MovingWindow):
newtroll = PesterProfile(h) newtroll = PesterProfile(h)
self.trollslum.addTroll(newtroll) self.trollslum.addTroll(newtroll)
self.moodRequest.emit(newtroll) self.moodRequest.emit(newtroll)
self.blockedChum.emit(handle) if not self.config.irc_compatibility_mode():
self.blockedChum.emit(handle)
@QtCore.pyqtSlot(QString) @QtCore.pyqtSlot(QString)
def unblockChum(self, handle): def unblockChum(self, handle):
@ -2902,8 +2904,9 @@ class PesterWindow(MovingWindow):
self.trollslum.removeTroll(handle) self.trollslum.removeTroll(handle)
self.config.addChum(chum) self.config.addChum(chum)
self.chumList.addChum(chum) self.chumList.addChum(chum)
self.moodRequest.emit(chum) if not self.config.irc_compatibility_mode():
self.unblockedChum.emit(handle) self.moodRequest.emit(chum)
self.unblockedChum.emit(handle)
@QtCore.pyqtSlot(bool) @QtCore.pyqtSlot(bool)
def toggleIdle(self, idle): def toggleIdle(self, idle):
@ -2968,7 +2971,7 @@ class PesterWindow(MovingWindow):
# might affect, but I've been using it for months and haven't # might affect, but I've been using it for months and haven't
# noticed any issues.... # noticed any issues....
handle = convo.chum.handle handle = convo.chum.handle
if self.isBot(handle): if self.isBot(handle) and not self.config.irc_compatibility_mode():
# Don't send these idle messages. # Don't send these idle messages.
continue continue
# karxi: Now we just use 'handle' instead of 'h'. # karxi: Now we just use 'handle' instead of 'h'.
@ -3439,15 +3442,20 @@ class PesterWindow(MovingWindow):
curnotify = self.config.notifyOptions() curnotify = self.config.notifyOptions()
if notifysetting != curnotify: if notifysetting != curnotify:
self.config.set("notifyOptions", notifysetting) self.config.set("notifyOptions", notifysetting)
# low bandwidth # IRC compatibility (previously low bandwidth)
bandwidthsetting = self.optionmenu.bandwidthcheck.isChecked() irc_mode_setting = self.optionmenu.irc_mode_check.isChecked()
curbandwidth = self.config.lowBandwidth() current_irc_mode = self.config.irc_compatibility_mode()
if bandwidthsetting != curbandwidth: if irc_mode_setting != current_irc_mode:
self.config.set("lowBandwidth", bandwidthsetting) self.config.set("irc_compatibility_mode", irc_mode_setting)
if bandwidthsetting: if irc_mode_setting:
self.leftChannel.emit("#pesterchum") self.leftChannel.emit("#pesterchum")
else: else:
self.joinChannel.emit("#pesterchum") self.joinChannel.emit("#pesterchum")
# Force prefix
force_prefix_setting = self.optionmenu.force_prefix_check.isChecked()
current_prefix_setting = self.config.force_prefix()
if force_prefix_setting != current_prefix_setting:
self.config.set("force_prefix", force_prefix_setting)
# nickserv # nickserv
autoidentify = self.optionmenu.autonickserv.isChecked() autoidentify = self.optionmenu.autonickserv.isChecked()
nickservpass = self.optionmenu.nickservpass.text() nickservpass = self.optionmenu.nickservpass.text()

View file

@ -358,8 +358,11 @@ with a backup from: <a href='%s'>%s</a></h3></html>"
"notifyOptions", self.SIGNIN | self.NEWMSG | self.NEWCONVO | self.INITIALS "notifyOptions", self.SIGNIN | self.NEWMSG | self.NEWCONVO | self.INITIALS
) )
def lowBandwidth(self): def irc_compatibility_mode(self):
return self.config.get("lowBandwidth", False) return self.config.get("irc_compatibility_mode", False)
def force_prefix(self):
return self.config.get("force_prefix", True)
def ghostchum(self): def ghostchum(self):
return self.config.get("ghostchum", False) return self.config.get("ghostchum", False)