Add oyoyo license notice, do further linting

This commit is contained in:
Dpeta 2023-02-10 01:29:23 +01:00
parent 5f50f6420f
commit c88a58a5a2
No known key found for this signature in database
GPG key ID: 51227517CEA0030C

111
irc.py
View file

@ -6,6 +6,27 @@ References for the IRC protocol:
- Modern IRC client protocol writeup: https://modern.ircdocs.horse - Modern IRC client protocol writeup: https://modern.ircdocs.horse
- IRCv3 protocol additions: https://ircv3.net/irc/ - IRCv3 protocol additions: https://ircv3.net/irc/
- Draft of metadata specification: https://gist.github.com/k4bek4be/92c2937cefd49990fbebd001faf2b237 - Draft of metadata specification: https://gist.github.com/k4bek4be/92c2937cefd49990fbebd001faf2b237
Some code in this file may be based on the oyoyo IRC library,
the license notice included with oyoyo source files is indented here:
# Copyright (c) 2008 Duncan Fordyce
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
""" """
import sys import sys
import socket import socket
@ -54,6 +75,7 @@ except ImportError:
class PesterIRC(QtCore.QThread): class PesterIRC(QtCore.QThread):
"""Class for making a thread that manages the connection to server."""
def __init__(self, config, window, verify_hostname=True): def __init__(self, config, window, verify_hostname=True):
QtCore.QThread.__init__(self) QtCore.QThread.__init__(self)
self.mainwindow = window self.mainwindow = window
@ -148,14 +170,14 @@ class PesterIRC(QtCore.QThread):
return default_context return default_context
def connect(self, verify_hostname=True): def connect(self, verify_hostname=True):
"""initiates the connection to the server set in self.host:self.port """Initiates the connection to the server set in self.host:self.port
self.ssl decides whether the connection uses ssl. self.ssl decides whether the connection uses ssl.
Certificate validation when using SSL/TLS may be disabled by Certificate validation when using SSL/TLS may be disabled by
passing the 'verify_hostname' parameter. The user is asked if they passing the 'verify_hostname' parameter. The user is asked if they
want to disable it if this functions raises a certificate validation error, want to disable it if this functions raises a certificate validation error,
in which case the function may be called again with 'verify_hostname'.""" in which case the function may be called again with 'verify_hostname'."""
PchumLog.info("connecting to {}:{}".format(self.host, self.port)) PchumLog.info("Connecting to %s:%s", self.host, self.port)
# Open connection # Open connection
plaintext_socket = socket.create_connection((self.host, self.port)) plaintext_socket = socket.create_connection((self.host, self.port))
@ -180,14 +202,14 @@ class PesterIRC(QtCore.QThread):
# self.connect_cb(self) # self.connect_cb(self)
def conn_generator(self): def conn_generator(self):
"""returns a generator object.""" """Returns a generator object."""
try: try:
buffer = b"" buffer = b""
while not self._end: while not self._end:
try: try:
buffer += self.socket.recv(1024) buffer += self.socket.recv(1024)
except OSError as e: except OSError as e:
PchumLog.warning("conn exception {} in {}".format(e, self)) PchumLog.warning("conn_generator exception %s in %s", e, self)
if self._end: if self._end:
break break
raise e raise e
@ -216,22 +238,23 @@ class PesterIRC(QtCore.QThread):
PchumLog.debug("passing timeout") PchumLog.debug("passing timeout")
raise se raise se
except (OSError, ssl.SSLEOFError) as se: except (OSError, ssl.SSLEOFError) as se:
PchumLog.debug("problem: %s" % (str(se))) PchumLog.warning("Problem: %s", se)
if self.socket: if self.socket:
PchumLog.info("error: closing socket") PchumLog.info("Error: closing socket.")
self.socket.close() self.socket.close()
raise se raise se
except Exception as e: except Exception as e:
PchumLog.exception("Non-socket exception in conn_generator().") PchumLog.exception("Non-socket exception in conn_generator().")
raise e raise e
else: else:
PchumLog.debug("ending while, end is %s" % self._end) PchumLog.debug("Ending conn() while loop, end is %s.", self._end)
if self.socket: if self.socket:
PchumLog.info("finished: closing socket") PchumLog.info("Finished: closing socket.")
self.socket.close() self.socket.close()
yield False yield False
def parse_irc_line(self, line: str): def parse_irc_line(self, line: str):
"""Retrieves tags, prefix, command, and arguments from an unparsed IRC line."""
parts = line.split(" ") parts = line.split(" ")
tags = None tags = None
prefix = None prefix = None
@ -262,10 +285,9 @@ class PesterIRC(QtCore.QThread):
return (tags, prefix, command, fused_args) return (tags, prefix, command, fused_args)
def close(self): def close(self):
# with extreme prejudice """Kill the socket 'with extreme prejudice'."""
if self.socket: if self.socket:
PchumLog.info("shutdown socket") PchumLog.info("close() was called, shutting down socket.")
# print("shutdown socket")
self._end = True self._end = True
try: try:
self.socket.shutdown(socket.SHUT_RDWR) self.socket.shutdown(socket.SHUT_RDWR)
@ -286,6 +308,7 @@ class PesterIRC(QtCore.QThread):
self.conn = self.conn_generator() self.conn = self.conn_generator()
def run(self): def run(self):
"""Connect and run update loop."""
try: try:
self.IRCConnect() self.IRCConnect()
except OSError as se: except OSError as se:
@ -304,11 +327,11 @@ class PesterIRC(QtCore.QThread):
return return
except (OSError, IndexError, ValueError) as se: except (OSError, IndexError, ValueError) as se:
self.stopIRC = "{}, {}".format(type(se), se) self.stopIRC = "{}, {}".format(type(se), se)
PchumLog.debug("socket error, exiting thread") PchumLog.debug("Socket error, exiting thread.")
return return
else: else:
if not res: if not res:
PchumLog.debug("false Yield: %s, returning" % res) PchumLog.debug("False Yield: %s, returning", res)
return return
def setConnected(self): def setConnected(self):
@ -326,6 +349,7 @@ class PesterIRC(QtCore.QThread):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def updateIRC(self): def updateIRC(self):
"""Get a silly scrunkler from the generator!!"""
try: try:
res = next(self.conn) res = next(self.conn)
except socket.timeout as se: except socket.timeout as se:
@ -451,9 +475,7 @@ class PesterIRC(QtCore.QThread):
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def updateProfile(self): def updateProfile(self):
me = self.mainwindow.profile() self.send_irc.nick(self.mainwindow.profile().handle)
handle = me.handle
self.send_irc.nick(handle)
self.mainwindow.closeConversations(True) self.mainwindow.closeConversations(True)
self.mainwindow.doAutoIdentify() self.mainwindow.doAutoIdentify()
self.mainwindow.autoJoinDone = False self.mainwindow.autoJoinDone = False
@ -477,7 +499,7 @@ class PesterIRC(QtCore.QThread):
for convo in list(self.mainwindow.convos.keys()): for convo in list(self.mainwindow.convos.keys()):
self.send_irc.privmsg( self.send_irc.privmsg(
convo, convo,
"COLOR >%s" % (self.mainwindow.profile().colorcmd()), f"COLOR >{self.mainwindow.profile().colorcmd()}",
) )
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
@ -555,19 +577,19 @@ class PesterIRC(QtCore.QThread):
else: else:
self.noticeReceived.emit(handle, msg) self.noticeReceived.emit(handle, msg)
def metadata(self, target, nick, key, visibility, value): def metadata(self, _target, nick, key, _visibility, value):
"""METADATA DRAFT metadata message from server. """METADATA DRAFT metadata message from server.
The format of the METADATA server notication is: The format of the METADATA server notication is:
METADATA <Target> <Key> <Visibility> <Value> METADATA <Target> <Key> <Visibility> <Value>
""" """
if key.lower() == "mood": if key.casefold() == "mood":
try: try:
mood = Mood(int(value)) mood = Mood(int(value))
self.moodUpdated.emit(nick, mood) self.moodUpdated.emit(nick, mood)
except ValueError: except ValueError:
PchumLog.warning("Invalid mood value, %s, %s", nick, mood) PchumLog.warning("Invalid mood value, %s, %s", nick, mood)
elif key.lower() == "color": elif key.casefold() == "color":
color = QtGui.QColor(value) # Invalid color becomes rgb 0,0,0 color = QtGui.QColor(value) # Invalid color becomes rgb 0,0,0
self.colorUpdated.emit(nick, color) self.colorUpdated.emit(nick, color)
@ -633,10 +655,10 @@ class PesterIRC(QtCore.QThread):
msg = "/me" + msg[7:-1] msg = "/me" + msg[7:-1]
# CTCPs that don't need to be shown # CTCPs that don't need to be shown
elif msg[0] == "\x01": elif msg[0] == "\x01":
PchumLog.info('---> recv "CTCP {} :{}"'.format(handle, msg[1:-1])) PchumLog.info('---> recv "CTCP %s :%s"', handle, msg[1:-1])
# VERSION, return version # VERSION, return version
if msg[1:-1].startswith("VERSION"): if msg[1:-1].startswith("VERSION"):
self.send_irc.ctcp(handle, "VERSION", "Pesterchum %s" % (_pcVersion)) self.send_irc.ctcp(handle, "VERSION", "Pesterchum {_pcVersion}")
# CLIENTINFO, return supported CTCP commands. # CLIENTINFO, return supported CTCP commands.
elif msg[1:-1].startswith("CLIENTINFO"): elif msg[1:-1].startswith("CLIENTINFO"):
self.send_irc.ctcp( self.send_irc.ctcp(
@ -666,14 +688,14 @@ class PesterIRC(QtCore.QThread):
# GETMOOD via CTCP # GETMOOD via CTCP
# Maybe we can do moods like this in the future... # Maybe we can do moods like this in the future...
mymood = self.mainwindow.profile().mood.value() mymood = self.mainwindow.profile().mood.value()
self.send_irc.ctcp(handle, "MOOD >%d" % (mymood)) self.send_irc.ctcp(handle, f"MOOD >{mymood}")
# Backwards compatibility # Backwards compatibility
self.send_irc.privmsg("#pesterchum", "MOOD >%d" % (mymood)) self.send_irc.privmsg(f"#pesterchum", f"MOOD >{mymood}")
return return
if chan != "#pesterchum": if chan != "#pesterchum":
# We don't need anywhere near that much spam. # We don't need anywhere near that much spam.
PchumLog.info('---> recv "PRIVMSG {} :{}"'.format(handle, msg)) PchumLog.info('---> recv "PRIVMSG %s :%s"', handle, msg)
if chan == "#pesterchum": if chan == "#pesterchum":
# follow instructions # follow instructions
@ -694,10 +716,7 @@ class PesterIRC(QtCore.QThread):
else: else:
self.memoReceived.emit(chan, handle, msg) self.memoReceived.emit(chan, handle, msg)
else: else:
# private message # Normal PRIVMSG messages (the normal kind!!)
# silently ignore messages to yourself.
if handle == self.mainwindow.profile().handle:
return
if msg[0:7] == "COLOR >": if msg[0:7] == "COLOR >":
colors = msg[7:].split(",") colors = msg[7:].split(",")
try: try:
@ -705,7 +724,7 @@ class PesterIRC(QtCore.QThread):
except ValueError as e: except ValueError as e:
PchumLog.warning(e) PchumLog.warning(e)
colors = [0, 0, 0] colors = [0, 0, 0]
PchumLog.debug("colors: " + str(colors)) PchumLog.debug("colors: %s", colors)
color = QtGui.QColor(*colors) color = QtGui.QColor(*colors)
self.colorUpdated.emit(handle, color) self.colorUpdated.emit(handle, color)
else: else:
@ -734,7 +753,7 @@ class PesterIRC(QtCore.QThread):
def part(self, nick, channel, _reason="nanchos"): def part(self, nick, channel, _reason="nanchos"):
"""'PART' message from server, someone left a channel.""" """'PART' message from server, someone left a channel."""
handle = nick[0 : nick.find("!")] handle = nick[0 : nick.find("!")]
PchumLog.info('---> recv "PART {}: {}"'.format(handle, channel)) PchumLog.info('---> recv "PART %s: %s"', handle, channel)
self.userPresentUpdate.emit(handle, channel, "left") self.userPresentUpdate.emit(handle, channel, "left")
if channel == "#pesterchum": if channel == "#pesterchum":
self.moodUpdated.emit(handle, Mood("offline")) self.moodUpdated.emit(handle, Mood("offline"))
@ -742,7 +761,7 @@ class PesterIRC(QtCore.QThread):
def join(self, nick, channel): def join(self, nick, channel):
"""'JOIN' message from server, someone joined a channel.""" """'JOIN' message from server, someone joined a channel."""
handle = nick[0 : nick.find("!")] handle = nick[0 : nick.find("!")]
PchumLog.info('---> recv "JOIN {}: {}"'.format(handle, channel)) PchumLog.info('---> recv "JOIN %s: %s"', handle, channel)
self.userPresentUpdate.emit(handle, channel, "join") self.userPresentUpdate.emit(handle, channel, "join")
if channel == "#pesterchum": if channel == "#pesterchum":
if handle == self.mainwindow.randhandler.randNick: if handle == self.mainwindow.randhandler.randNick:
@ -830,7 +849,7 @@ class PesterIRC(QtCore.QThread):
elif newnick == self.mainwindow.randhandler.randNick: elif newnick == self.mainwindow.randhandler.randNick:
self.mainwindow.randhandler.setRunning(True) self.mainwindow.randhandler.setRunning(True)
def welcome(self, server, nick, msg): def welcome(self, _server, _nick, _msg):
"""Numeric reply 001 RPL_WELCOME, send when we've connected to the server.""" """Numeric reply 001 RPL_WELCOME, send when we've connected to the server."""
self.setConnected() self.setConnected()
# mychumhandle = self.mainwindow.profile().handle # mychumhandle = self.mainwindow.profile().handle
@ -845,7 +864,6 @@ class PesterIRC(QtCore.QThread):
self.send_irc.cap( self.send_irc.cap(
self, "REQ", "pesterchum-tag" self, "REQ", "pesterchum-tag"
) # <--- Currently not using this ) # <--- Currently not using this
# time.sleep(0.413 + 0.097) # <--- somehow, this actually helps.
self.send_irc.join("#pesterchum") self.send_irc.join("#pesterchum")
# Moods via metadata # Moods via metadata
self.send_irc.metadata("*", "sub", "mood") self.send_irc.metadata("*", "sub", "mood")
@ -854,7 +872,7 @@ class PesterIRC(QtCore.QThread):
self.send_irc.metadata("*", "sub", "color") self.send_irc.metadata("*", "sub", "color")
self.send_irc.metadata("*", "set", "color", str(color.name())) self.send_irc.metadata("*", "set", "color", str(color.name()))
# Backwards compatible moods # Backwards compatible moods
self.send_irc.privmsg("#pesterchum", "MOOD >%d" % (mymood)) self.send_irc.privmsg("#pesterchum", f"MOOD >{mymood}")
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.
@ -864,8 +882,8 @@ class PesterIRC(QtCore.QThread):
""" """
features = params[:-1] features = params[:-1]
PchumLog.info("Server featurelist: %s", features) PchumLog.info("Server featurelist: %s", features)
for x in features: for feature in features:
if x.upper().startswith("METADATA"): if feature.casefold().startswith("metadata"):
PchumLog.info("Server supports metadata.") PchumLog.info("Server supports metadata.")
self.metadata_supported = True self.metadata_supported = True
@ -954,15 +972,18 @@ class PesterIRC(QtCore.QThread):
def nicknameinuse(self, _server, _cmd, nick, _msg): def nicknameinuse(self, _server, _cmd, nick, _msg):
"""Numerical reply 433 ERR_NICKNAMEINUSE, raised when changing nick to nick in use.""" """Numerical reply 433 ERR_NICKNAMEINUSE, raised when changing nick to nick in use."""
newnick = "pesterClient%d" % (random.randint(100, 999)) self._reset_nick(nick)
self.send_irc.nick(newnick)
self.nickCollision.emit(nick, newnick)
def nickcollision(self, _server, _cmd, nick, _msg): def nickcollision(self, _server, _cmd, nick, _msg):
"""Numerical reply 436 ERR_NICKCOLLISION, raised during connect when nick is in use.""" """Numerical reply 436 ERR_NICKCOLLISION, raised during connect when nick is in use."""
newnick = "pesterClient%d" % (random.randint(100, 999)) self._reset_nick(nick)
def _reset_nick(self, oldnick):
"""Set our nick to a random pesterClient."""
random_number = int(random.random() * 9999) # Random int in range 1000 <---> 9999
newnick = f"pesterClient{random_number}"
self.send_irc.nick(newnick) self.send_irc.nick(newnick)
self.nickCollision.emit(nick, newnick) self.nickCollision.emit(oldnick, newnick)
def forbiddenchannel(self, _server, handle, channel, msg): def forbiddenchannel(self, _server, handle, channel, msg):
"""Numeric reply 448 'forbiddenchannel' reply, channel is forbidden. """Numeric reply 448 'forbiddenchannel' reply, channel is forbidden.
@ -975,7 +996,7 @@ class PesterIRC(QtCore.QThread):
"""Numeric reply 473 ERR_INVITEONLYCHAN, can't join channel (+i).""" """Numeric reply 473 ERR_INVITEONLYCHAN, can't join channel (+i)."""
self.chanInviteOnly.emit(channel) self.chanInviteOnly.emit(channel)
def keyvalue(self, target, handle_us, handle_owner, key, visibility, *value): def keyvalue(self, _target, _handle_us, handle_owner, key, _visibility, *value):
"""METADATA DRAFT numeric reply 761 RPL_KEYVALUE, we received the value of a key. """METADATA DRAFT numeric reply 761 RPL_KEYVALUE, we received the value of a key.
The format of the METADATA server notication is: The format of the METADATA server notication is:
@ -1014,9 +1035,9 @@ class PesterIRC(QtCore.QThread):
def run_command(self, command, *args): def run_command(self, command, *args):
"""Finds and runs a command.""" """Finds and runs a command."""
PchumLog.debug("run_command %s(%s)", command, args) PchumLog.debug("run_command %s(%s)", command, args)
try: if command in self.commands:
command_function = self.commands[command] command_function = self.commands[command]
except KeyError: else:
PchumLog.warning("No matching function for command: %s(%s)", command, args) PchumLog.warning("No matching function for command: %s(%s)", command, args)
return return