Restructure networking (WIP)
This commit is contained in:
parent
aaec47b96d
commit
8749c9586a
4 changed files with 556 additions and 156 deletions
571
irc.py
571
irc.py
|
@ -3,6 +3,10 @@ import socket
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
import ssl
|
import ssl
|
||||||
|
import sys
|
||||||
|
import select
|
||||||
|
import datetime
|
||||||
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt6 import QtCore, QtGui
|
from PyQt6 import QtCore, QtGui
|
||||||
|
@ -15,9 +19,10 @@ from dataobjs import PesterProfile
|
||||||
from generic import PesterList
|
from generic import PesterList
|
||||||
from version import _pcVersion
|
from version import _pcVersion
|
||||||
|
|
||||||
from oyoyo.client import IRCClient
|
from oyoyo import services
|
||||||
from oyoyo.cmdhandler import DefaultCommandHandler
|
from oyoyo.parse import parse_raw_irc_command
|
||||||
from oyoyo import helpers, services
|
|
||||||
|
import scripts.irc.outgoing
|
||||||
|
|
||||||
PchumLog = logging.getLogger("pchumLogger")
|
PchumLog = logging.getLogger("pchumLogger")
|
||||||
SERVICES = [
|
SERVICES = [
|
||||||
|
@ -30,6 +35,20 @@ SERVICES = [
|
||||||
"botserv",
|
"botserv",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class CommandError(Exception):
|
||||||
|
def __init__(self, cmd):
|
||||||
|
self.cmd = cmd
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchCommandError(CommandError):
|
||||||
|
def __str__(self):
|
||||||
|
return 'No such command "%s"' % ".".join(self.cmd)
|
||||||
|
|
||||||
|
|
||||||
|
class ProtectedCommandError(CommandError):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Command "%s" is protected' % ".".join(self.cmd)
|
||||||
|
|
||||||
# Python 3
|
# Python 3
|
||||||
QString = str
|
QString = str
|
||||||
|
|
||||||
|
@ -44,6 +63,24 @@ QString = str
|
||||||
# # karxi; We do NOT need this set to INFO; it's very, very spammy.
|
# # karxi; We do NOT need this set to INFO; it's very, very spammy.
|
||||||
# logging.basicConfig(level=logging.WARNING)
|
# logging.basicConfig(level=logging.WARNING)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import certifi
|
||||||
|
except ImportError:
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
# Certifi is required to validate certificates on MacOS with pyinstaller builds.
|
||||||
|
PchumLog.warning(
|
||||||
|
"Failed to import certifi, which is recommended on MacOS. "
|
||||||
|
"Pesterchum might not be able to validate certificates unless "
|
||||||
|
"Python's root certs are installed."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
PchumLog.info(
|
||||||
|
"Failed to import certifi, Pesterchum will not be able to validate "
|
||||||
|
"certificates if the system-provided root certificates are invalid."
|
||||||
|
)
|
||||||
|
|
||||||
|
class IRCClientError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class PesterIRC(QtCore.QThread):
|
class PesterIRC(QtCore.QThread):
|
||||||
def __init__(self, config, window, verify_hostname=True):
|
def __init__(self, config, window, verify_hostname=True):
|
||||||
|
@ -58,26 +95,228 @@ class PesterIRC(QtCore.QThread):
|
||||||
self.NickServ = services.NickServ()
|
self.NickServ = services.NickServ()
|
||||||
self.ChanServ = services.ChanServ()
|
self.ChanServ = services.ChanServ()
|
||||||
|
|
||||||
def IRCConnect(self):
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.cli = IRCClient(
|
self.host = self.config.server()
|
||||||
PesterHandler,
|
self.port = self.config.port()
|
||||||
host=self.config.server(),
|
self.ssl = self.config.ssl()
|
||||||
port=self.config.port(),
|
self.nick = self.mainwindow.profile().handle
|
||||||
ssl=self.config.ssl(),
|
self.timeout = 120
|
||||||
nick=self.mainwindow.profile().handle,
|
self.blocking = True
|
||||||
username="pcc31",
|
self._end = False
|
||||||
realname="pcc31",
|
|
||||||
timeout=120,
|
self.command_handler = self
|
||||||
|
self.parent = self
|
||||||
|
|
||||||
|
self.send_irc = scripts.irc.outgoing.send_irc()
|
||||||
|
|
||||||
|
def get_ssl_context(self):
|
||||||
|
"""Returns an SSL context for connecting over SSL/TLS.
|
||||||
|
Loads the certifi root certificate bundle if the certifi module is less
|
||||||
|
than a year old or if the system certificate store is empty.
|
||||||
|
|
||||||
|
The cert store on Windows also seems to have issues, so it's better
|
||||||
|
to use the certifi provided bundle assuming it's a recent version.
|
||||||
|
|
||||||
|
On MacOS the system cert store is usually empty, as Python does not use
|
||||||
|
the system provided ones, instead relying on a bundle installed with the
|
||||||
|
python installer."""
|
||||||
|
default_context = ssl.create_default_context()
|
||||||
|
if "certifi" not in sys.modules:
|
||||||
|
return default_context
|
||||||
|
|
||||||
|
# Get age of certifi module
|
||||||
|
certifi_date = datetime.datetime.strptime(certifi.__version__, "%Y.%m.%d")
|
||||||
|
current_date = datetime.datetime.now()
|
||||||
|
certifi_age = current_date - certifi_date
|
||||||
|
|
||||||
|
empty_cert_store = (
|
||||||
|
list(default_context.cert_store_stats().values()).count(0) == 3
|
||||||
)
|
)
|
||||||
self.cli.command_handler.parent = self
|
# 31557600 seconds is approximately 1 year
|
||||||
self.cli.command_handler.mainwindow = self.mainwindow
|
if empty_cert_store or certifi_age.total_seconds() <= 31557600:
|
||||||
|
PchumLog.info(
|
||||||
|
"Using SSL/TLS context with certifi-provided root certificates."
|
||||||
|
)
|
||||||
|
return ssl.create_default_context(cafile=certifi.where())
|
||||||
|
PchumLog.info("Using SSL/TLS context with system-provided root certificates.")
|
||||||
|
return default_context
|
||||||
|
|
||||||
|
def connect(self, verify_hostname=True):
|
||||||
|
"""initiates the connection to the server set in self.host:self.port
|
||||||
|
self.ssl decides whether the connection uses ssl.
|
||||||
|
|
||||||
|
Certificate validation when using SSL/TLS may be disabled by
|
||||||
|
passing the 'verify_hostname' parameter. The user is asked if they
|
||||||
|
want to disable it if this functions raises a certificate validation error,
|
||||||
|
in which case the function may be called again with 'verify_hostname'."""
|
||||||
|
PchumLog.info("connecting to {}:{}".format(self.host, self.port))
|
||||||
|
|
||||||
|
# Open connection
|
||||||
|
plaintext_socket = socket.create_connection((self.host, self.port))
|
||||||
|
|
||||||
|
if self.ssl:
|
||||||
|
# Upgrade connection to use SSL/TLS if enabled
|
||||||
|
context = self.get_ssl_context()
|
||||||
|
context.check_hostname = verify_hostname
|
||||||
|
self.socket = context.wrap_socket(
|
||||||
|
plaintext_socket, server_hostname=self.host
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# SSL/TLS is disabled, connection is plaintext
|
||||||
|
self.socket = plaintext_socket
|
||||||
|
|
||||||
|
self.send_irc.socket = self.socket
|
||||||
|
|
||||||
|
# setblocking is a shorthand for timeout,
|
||||||
|
# we shouldn't use both.
|
||||||
|
if self.timeout:
|
||||||
|
self.socket.settimeout(self.timeout)
|
||||||
|
elif not self.blocking:
|
||||||
|
self.socket.setblocking(False)
|
||||||
|
elif self.blocking:
|
||||||
|
self.socket.setblocking(True)
|
||||||
|
|
||||||
|
self.send_irc.nick(self.nick)
|
||||||
|
self.send_irc.user("pcc31", "pcc31")
|
||||||
|
# if self.connect_cb:
|
||||||
|
# self.connect_cb(self)
|
||||||
|
|
||||||
|
def conn(self):
|
||||||
|
"""returns a generator object."""
|
||||||
try:
|
try:
|
||||||
self.cli.connect(self.verify_hostname)
|
buffer = b""
|
||||||
|
while not self._end:
|
||||||
|
# Block for connection-killing exceptions
|
||||||
|
try:
|
||||||
|
tries = 1
|
||||||
|
while tries < 10:
|
||||||
|
# Check if alive
|
||||||
|
if self._end == True:
|
||||||
|
break
|
||||||
|
if self.socket.fileno() == -1:
|
||||||
|
self._end = True
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
ready_to_read, ready_to_write, in_error = select.select(
|
||||||
|
[self.socket], [], []
|
||||||
|
)
|
||||||
|
for x in ready_to_read:
|
||||||
|
buffer += x.recv(1024)
|
||||||
|
break
|
||||||
|
except ssl.SSLWantReadError as e:
|
||||||
|
PchumLog.warning("ssl.SSLWantReadError on send, " + str(e))
|
||||||
|
select.select([self.socket], [], [])
|
||||||
|
if tries >= 9:
|
||||||
|
raise e
|
||||||
|
except ssl.SSLWantWriteError as e:
|
||||||
|
PchumLog.warning("ssl.SSLWantWriteError on send, " + str(e))
|
||||||
|
select.select([], [self.socket], [])
|
||||||
|
if tries >= 9:
|
||||||
|
raise e
|
||||||
|
except ssl.SSLEOFError as e:
|
||||||
|
# ssl.SSLEOFError guarantees a broken connection.
|
||||||
|
PchumLog.warning("ssl.SSLEOFError in on send, " + str(e))
|
||||||
|
raise e
|
||||||
|
except (socket.timeout, TimeoutError) as e:
|
||||||
|
# socket.timeout is deprecated in 3.10
|
||||||
|
PchumLog.warning("TimeoutError in on send, " + str(e))
|
||||||
|
raise socket.timeout
|
||||||
|
except (OSError, IndexError, ValueError, Exception) as e:
|
||||||
|
PchumLog.debug("Miscellaneous exception in conn, " + str(e))
|
||||||
|
if tries >= 9:
|
||||||
|
raise e
|
||||||
|
tries += 1
|
||||||
|
PchumLog.debug(
|
||||||
|
"Possibly retrying recv. (attempt %s)" % str(tries)
|
||||||
|
)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
except socket.timeout as e:
|
||||||
|
PchumLog.warning("timeout in client.py, " + str(e))
|
||||||
|
if self._end:
|
||||||
|
break
|
||||||
|
raise e
|
||||||
|
except ssl.SSLEOFError as e:
|
||||||
|
raise e
|
||||||
|
except OSError as e:
|
||||||
|
PchumLog.warning("conn exception {} in {}".format(e, self))
|
||||||
|
if self._end:
|
||||||
|
break
|
||||||
|
if not self.blocking and e.errno == 11:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
if self._end:
|
||||||
|
break
|
||||||
|
if len(buffer) == 0 and self.blocking:
|
||||||
|
PchumLog.debug("len(buffer) = 0")
|
||||||
|
raise OSError("Connection closed")
|
||||||
|
|
||||||
|
data = buffer.split(bytes("\n", "UTF-8"))
|
||||||
|
buffer = data.pop()
|
||||||
|
|
||||||
|
PchumLog.debug("data = " + str(data))
|
||||||
|
|
||||||
|
for el in data:
|
||||||
|
tags, prefix, command, args = parse_raw_irc_command(el)
|
||||||
|
# print(tags, prefix, command, args)
|
||||||
|
try:
|
||||||
|
# Only need tags with tagmsg
|
||||||
|
if command.upper() == "TAGMSG":
|
||||||
|
self.run_command(command, prefix, tags, *args)
|
||||||
|
else:
|
||||||
|
self.run_command(command, prefix, *args)
|
||||||
|
except CommandError as e:
|
||||||
|
PchumLog.warning("CommandError %s" % str(e))
|
||||||
|
|
||||||
|
yield True
|
||||||
|
except socket.timeout as se:
|
||||||
|
PchumLog.debug("passing timeout")
|
||||||
|
raise se
|
||||||
|
except (OSError, ssl.SSLEOFError) as se:
|
||||||
|
PchumLog.debug("problem: %s" % (str(se)))
|
||||||
|
if self.socket:
|
||||||
|
PchumLog.info("error: closing socket")
|
||||||
|
self.socket.close()
|
||||||
|
raise se
|
||||||
|
except Exception as e:
|
||||||
|
PchumLog.debug("other exception: %s" % str(e))
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
PchumLog.debug("ending while, end is %s" % self._end)
|
||||||
|
if self.socket:
|
||||||
|
PchumLog.info("finished: closing socket")
|
||||||
|
self.socket.close()
|
||||||
|
yield False
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
# with extreme prejudice
|
||||||
|
if self.socket:
|
||||||
|
PchumLog.info("shutdown socket")
|
||||||
|
# print("shutdown socket")
|
||||||
|
self._end = True
|
||||||
|
try:
|
||||||
|
self.socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
except OSError as e:
|
||||||
|
PchumLog.debug(
|
||||||
|
"Error while shutting down socket, already broken? %s" % str(e)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.socket.close()
|
||||||
|
except OSError as e:
|
||||||
|
PchumLog.debug(
|
||||||
|
"Error while closing socket, already broken? %s" % str(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
def IRCConnect(self):
|
||||||
|
try:
|
||||||
|
self.connect(self.verify_hostname)
|
||||||
except ssl.SSLCertVerificationError as e:
|
except ssl.SSLCertVerificationError as e:
|
||||||
# Ask if users wants to connect anyway
|
# Ask if users wants to connect anyway
|
||||||
self.askToConnect.emit(e)
|
self.askToConnect.emit(e)
|
||||||
raise e
|
raise e
|
||||||
self.conn = self.cli.conn()
|
self.conn = self.conn()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
|
@ -93,7 +332,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
res = self.updateIRC()
|
res = self.updateIRC()
|
||||||
except socket.timeout as se:
|
except socket.timeout as se:
|
||||||
PchumLog.debug("timeout in thread %s" % (self))
|
PchumLog.debug("timeout in thread %s" % (self))
|
||||||
self.cli.close()
|
self.close()
|
||||||
self.stopIRC = "{}, {}".format(type(se), se)
|
self.stopIRC = "{}, {}".format(type(se), se)
|
||||||
return
|
return
|
||||||
except (OSError, IndexError, ValueError) as se:
|
except (OSError, IndexError, ValueError) as se:
|
||||||
|
@ -128,7 +367,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
except (OSError, ValueError, IndexError) as se:
|
except (OSError, ValueError, IndexError) as se:
|
||||||
raise se
|
raise se
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
self.conn = self.cli.conn()
|
self.conn = self.conn()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return res
|
return res
|
||||||
|
@ -136,12 +375,12 @@ class PesterIRC(QtCore.QThread):
|
||||||
@QtCore.pyqtSlot(PesterProfile)
|
@QtCore.pyqtSlot(PesterProfile)
|
||||||
def getMood(self, *chums):
|
def getMood(self, *chums):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
self.cli.command_handler.getMood(*chums)
|
self.command_handler.getMood(*chums)
|
||||||
|
|
||||||
@QtCore.pyqtSlot(PesterList)
|
@QtCore.pyqtSlot(PesterList)
|
||||||
def getMoods(self, chums):
|
def getMoods(self, chums):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
self.cli.command_handler.getMood(*chums)
|
self.command_handler.getMood(*chums)
|
||||||
|
|
||||||
@QtCore.pyqtSlot(QString, QString)
|
@QtCore.pyqtSlot(QString, QString)
|
||||||
def sendNotice(self, text, handle):
|
def sendNotice(self, text, handle):
|
||||||
|
@ -149,7 +388,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
t = str(text)
|
t = str(text)
|
||||||
try:
|
try:
|
||||||
helpers.notice(self.cli, h, t)
|
self.send_irc.notice(h, t)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -201,7 +440,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
textl = splittext(textl)
|
textl = splittext(textl)
|
||||||
try:
|
try:
|
||||||
for t in textl:
|
for t in textl:
|
||||||
helpers.msg(self.cli, h, t)
|
self.send_irc.msg(h, t)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -213,7 +452,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
def sendCTCP(self, handle, text):
|
def sendCTCP(self, handle, text):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
try:
|
try:
|
||||||
helpers.ctcp(self.cli, handle, text)
|
self.send_irc.ctcp(handle, text)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -223,11 +462,11 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(
|
self.send_irc.msg(
|
||||||
self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd())
|
self, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd())
|
||||||
)
|
)
|
||||||
if initiated:
|
if initiated:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:BEGIN")
|
self.send_irc.msg(h, "PESTERCHUM:BEGIN")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -237,7 +476,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:CEASE")
|
self.send_irc.msg(h, "PESTERCHUM:CEASE")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -248,7 +487,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
me = self.mainwindow.profile()
|
me = self.mainwindow.profile()
|
||||||
handle = me.handle
|
handle = me.handle
|
||||||
try:
|
try:
|
||||||
helpers.nick(self.cli, handle)
|
self.send_irc.nick(handle)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -264,13 +503,13 @@ class PesterIRC(QtCore.QThread):
|
||||||
me = self.mainwindow.profile()
|
me = self.mainwindow.profile()
|
||||||
# Moods via metadata
|
# Moods via metadata
|
||||||
try:
|
try:
|
||||||
helpers.metadata(self.cli, "*", "set", "mood", str(me.mood.value()))
|
self.send_irc.metadata("*", "set", "mood", str(me.mood.value()))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
# Backwards compatibility
|
# Backwards compatibility
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, "#pesterchum", "MOOD >%d" % (me.mood.value()))
|
self.send_irc.msg("#pesterchum", "MOOD >%d" % (me.mood.value()))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -283,15 +522,15 @@ class PesterIRC(QtCore.QThread):
|
||||||
# Update color metadata field
|
# Update color metadata field
|
||||||
try:
|
try:
|
||||||
color = self.mainwindow.profile().color
|
color = self.mainwindow.profile().color
|
||||||
helpers.metadata(self.cli, "*", "set", "color", str(color.name()))
|
self.send_irc.metadata("*", "set", "color", str(color.name()))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
# Send color messages
|
# Send color messages
|
||||||
for h in list(self.mainwindow.convos.keys()):
|
for h in list(self.mainwindow.convos.keys()):
|
||||||
try:
|
try:
|
||||||
helpers.msg(
|
self.send_irc.msg(
|
||||||
self.cli,
|
self,
|
||||||
h,
|
h,
|
||||||
"COLOR >%s" % (self.mainwindow.profile().colorcmd()),
|
"COLOR >%s" % (self.mainwindow.profile().colorcmd()),
|
||||||
)
|
)
|
||||||
|
@ -304,7 +543,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:BLOCK")
|
self.send_irc.msg(h, "PESTERCHUM:BLOCK")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -314,7 +553,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:UNBLOCK")
|
self.send_irc.msg(h, "PESTERCHUM:UNBLOCK")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -324,7 +563,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.names(self.cli, c)
|
self.send_irc.names(c)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -333,7 +572,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
def requestChannelList(self):
|
def requestChannelList(self):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
try:
|
try:
|
||||||
helpers.channel_list(self.cli)
|
self.send_irc.channel_list(self)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -343,8 +582,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.join(self.cli, c)
|
self.send_irc.join(c)
|
||||||
helpers.mode(self.cli, c, "", None)
|
self.send_irc.mode(c, "", None)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -354,8 +593,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.part(self.cli, c)
|
self.send_irc.part(c)
|
||||||
self.cli.command_handler.joined = False
|
self.command_handler.joined = False
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -374,32 +613,21 @@ class PesterIRC(QtCore.QThread):
|
||||||
else:
|
else:
|
||||||
reason = ""
|
reason = ""
|
||||||
try:
|
try:
|
||||||
helpers.kick(self.cli, h, c, reason)
|
self.send_irc.kick(channel, h, reason)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
|
||||||
@QtCore.pyqtSlot(QString, QString, QString)
|
@QtCore.pyqtSlot(QString, QString, QString)
|
||||||
def setChannelMode(self, channel, mode, command):
|
def setChannelMode(self, channel, mode, command):
|
||||||
if hasattr(self, "cli"):
|
self.send_irc.mode(channel, mode, command)
|
||||||
c = str(channel)
|
|
||||||
m = str(mode)
|
|
||||||
cmd = str(command)
|
|
||||||
PchumLog.debug("c={}\nm={}\ncmd={}".format(c, m, cmd))
|
|
||||||
if cmd == "":
|
|
||||||
cmd = None
|
|
||||||
try:
|
|
||||||
helpers.mode(self.cli, c, m, cmd)
|
|
||||||
except OSError as e:
|
|
||||||
PchumLog.warning(e)
|
|
||||||
self.setConnectionBroken()
|
|
||||||
|
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def channelNames(self, channel):
|
def channelNames(self, channel):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.names(self.cli, c)
|
self.send_irc.names(c)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -410,32 +638,29 @@ class PesterIRC(QtCore.QThread):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.invite(self.cli, h, c)
|
self.send_irc.invite(h, c)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def pingServer(self):
|
def pingServer(self):
|
||||||
if hasattr(self, "cli"):
|
try:
|
||||||
try:
|
self.send_irc.ping("B33")
|
||||||
if hasattr(self, "cli"):
|
except OSError as e:
|
||||||
self.cli.send("PING :B33")
|
PchumLog.warning(e)
|
||||||
except OSError as e:
|
self.setConnectionBroken()
|
||||||
PchumLog.warning(e)
|
|
||||||
self.setConnectionBroken()
|
|
||||||
|
|
||||||
@QtCore.pyqtSlot(bool)
|
@QtCore.pyqtSlot(bool)
|
||||||
def setAway(self, away=True):
|
def setAway(self, away=True):
|
||||||
if hasattr(self, "cli"):
|
try:
|
||||||
try:
|
if away:
|
||||||
if away:
|
self.away("Idle")
|
||||||
self.cli.send("AWAY Idle")
|
else:
|
||||||
else:
|
self.away()
|
||||||
self.cli.send("AWAY")
|
except OSError as e:
|
||||||
except OSError as e:
|
PchumLog.warning(e)
|
||||||
PchumLog.warning(e)
|
self.setConnectionBroken()
|
||||||
self.setConnectionBroken()
|
|
||||||
|
|
||||||
@QtCore.pyqtSlot(QString, QString)
|
@QtCore.pyqtSlot(QString, QString)
|
||||||
def killSomeQuirks(self, channel, handle):
|
def killSomeQuirks(self, channel, handle):
|
||||||
|
@ -443,7 +668,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.ctcp(self.cli, c, "NOQUIRKS", h)
|
self.send_irc.ctcp(c, "NOQUIRKS", h)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
@ -451,34 +676,11 @@ class PesterIRC(QtCore.QThread):
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def disconnectIRC(self):
|
def disconnectIRC(self):
|
||||||
if hasattr(self, "cli"):
|
if hasattr(self, "cli"):
|
||||||
helpers.quit(self.cli, _pcVersion + " <3")
|
self.send_irc.quit(_pcVersion + " <3")
|
||||||
self.cli._end = True
|
self._end = True
|
||||||
self.cli.close()
|
self.close()
|
||||||
|
|
||||||
moodUpdated = QtCore.pyqtSignal("QString", Mood)
|
|
||||||
colorUpdated = QtCore.pyqtSignal("QString", QtGui.QColor)
|
|
||||||
messageReceived = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
memoReceived = QtCore.pyqtSignal("QString", "QString", "QString")
|
|
||||||
noticeReceived = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
inviteReceived = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
timeCommand = QtCore.pyqtSignal("QString", "QString", "QString")
|
|
||||||
namesReceived = QtCore.pyqtSignal("QString", PesterList)
|
|
||||||
channelListReceived = QtCore.pyqtSignal(PesterList)
|
|
||||||
nickCollision = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
getSvsnickedOn = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
myHandleChanged = QtCore.pyqtSignal("QString")
|
|
||||||
chanInviteOnly = QtCore.pyqtSignal("QString")
|
|
||||||
modesUpdated = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
connected = QtCore.pyqtSignal()
|
|
||||||
askToConnect = QtCore.pyqtSignal(Exception)
|
|
||||||
userPresentUpdate = QtCore.pyqtSignal("QString", "QString", "QString")
|
|
||||||
cannotSendToChan = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
tooManyPeeps = QtCore.pyqtSignal()
|
|
||||||
quirkDisable = QtCore.pyqtSignal("QString", "QString", "QString")
|
|
||||||
forbiddenchannel = QtCore.pyqtSignal("QString", "QString")
|
|
||||||
|
|
||||||
|
|
||||||
class PesterHandler(DefaultCommandHandler):
|
|
||||||
def notice(self, nick, chan, msg):
|
def notice(self, nick, chan, msg):
|
||||||
handle = nick[0 : nick.find("!")]
|
handle = nick[0 : nick.find("!")]
|
||||||
PchumLog.info('---> recv "NOTICE {} :{}"'.format(handle, msg))
|
PchumLog.info('---> recv "NOTICE {} :{}"'.format(handle, msg))
|
||||||
|
@ -564,12 +766,12 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
PchumLog.info('---> recv "CTCP {} :{}"'.format(handle, msg[1:-1]))
|
PchumLog.info('---> recv "CTCP {} :{}"'.format(handle, msg[1:-1]))
|
||||||
# VERSION, return version
|
# VERSION, return version
|
||||||
if msg[1:-1].startswith("VERSION"):
|
if msg[1:-1].startswith("VERSION"):
|
||||||
helpers.ctcp_reply(
|
self.send_irc.ctcp_reply(
|
||||||
self.parent.cli, handle, "VERSION", "Pesterchum %s" % (_pcVersion)
|
self.parent.cli, handle, "VERSION", "Pesterchum %s" % (_pcVersion)
|
||||||
)
|
)
|
||||||
# CLIENTINFO, return supported CTCP commands.
|
# CLIENTINFO, return supported CTCP commands.
|
||||||
elif msg[1:-1].startswith("CLIENTINFO"):
|
elif msg[1:-1].startswith("CLIENTINFO"):
|
||||||
helpers.ctcp_reply(
|
self.send_irc.ctcp_reply(
|
||||||
self.parent.cli,
|
self.parent.cli,
|
||||||
handle,
|
handle,
|
||||||
"CLIENTINFO",
|
"CLIENTINFO",
|
||||||
|
@ -578,14 +780,14 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
# PING, return pong
|
# PING, return pong
|
||||||
elif msg[1:-1].startswith("PING"):
|
elif msg[1:-1].startswith("PING"):
|
||||||
if len(msg[1:-1].split("PING ")) > 1:
|
if len(msg[1:-1].split("PING ")) > 1:
|
||||||
helpers.ctcp_reply(
|
self.send_irc.ctcp_reply(
|
||||||
self.parent.cli, handle, "PING", msg[1:-1].split("PING ")[1]
|
self.parent.cli, handle, "PING", msg[1:-1].split("PING ")[1]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
helpers.ctcp_reply(self.parent.cli, handle, "PING")
|
self.send_irc.ctcp_reply(self.parent.cli, handle, "PING")
|
||||||
# SOURCE, return source
|
# SOURCE, return source
|
||||||
elif msg[1:-1].startswith("SOURCE"):
|
elif msg[1:-1].startswith("SOURCE"):
|
||||||
helpers.ctcp_reply(
|
self.send_irc.ctcp_reply(
|
||||||
self.parent.cli,
|
self.parent.cli,
|
||||||
handle,
|
handle,
|
||||||
"SOURCE",
|
"SOURCE",
|
||||||
|
@ -600,9 +802,9 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
# 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()
|
||||||
helpers.ctcp_reply(self.parent.cli, handle, "MOOD >%d" % (mymood))
|
self.send_irc.ctcp_reply(self.parent.cli, handle, "MOOD >%d" % (mymood))
|
||||||
# Backwards compatibility
|
# Backwards compatibility
|
||||||
helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood))
|
self.send_irc.msg("#pesterchum", "MOOD >%d" % (mymood))
|
||||||
return
|
return
|
||||||
|
|
||||||
if chan != "#pesterchum":
|
if chan != "#pesterchum":
|
||||||
|
@ -621,7 +823,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
mychumhandle = self.mainwindow.profile().handle
|
mychumhandle = self.mainwindow.profile().handle
|
||||||
mymood = self.mainwindow.profile().mood.value()
|
mymood = self.mainwindow.profile().mood.value()
|
||||||
if msg.find(mychumhandle, 8) != -1:
|
if msg.find(mychumhandle, 8) != -1:
|
||||||
helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood))
|
self.send_irc.msg("#pesterchum", "MOOD >%d" % (mymood))
|
||||||
elif chan[0] == "#":
|
elif chan[0] == "#":
|
||||||
if msg[0:16] == "PESTERCHUM:TIME>":
|
if msg[0:16] == "PESTERCHUM:TIME>":
|
||||||
self.parent.timeCommand.emit(chan, handle, msg[16:])
|
self.parent.timeCommand.emit(chan, handle, msg[16:])
|
||||||
|
@ -659,23 +861,23 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
color = self.mainwindow.profile().color
|
color = self.mainwindow.profile().color
|
||||||
if not self.mainwindow.config.lowBandwidth():
|
if not self.mainwindow.config.lowBandwidth():
|
||||||
# Negotiate capabilities
|
# Negotiate capabilities
|
||||||
helpers.cap(self.client, "REQ", "message-tags")
|
self.send_irc.cap("REQ", "message-tags")
|
||||||
helpers.cap(
|
self.send_irc.cap(
|
||||||
self.client, "REQ", "draft/metadata-notify-2"
|
self, "REQ", "draft/metadata-notify-2"
|
||||||
) # <--- Not required in the unreal5 module implementation
|
) # <--- Not required in the unreal5 module implementation
|
||||||
helpers.cap(
|
self.send_irc.cap(
|
||||||
self.client, "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.
|
time.sleep(0.413 + 0.097) # <--- somehow, this actually helps.
|
||||||
helpers.join(self.client, "#pesterchum")
|
self.send_irc.join("#pesterchum")
|
||||||
# Moods via metadata
|
# Moods via metadata
|
||||||
helpers.metadata(self.client, "*", "sub", "mood")
|
self.send_irc.metadata("*", "sub", "mood")
|
||||||
helpers.metadata(self.client, "*", "set", "mood", str(mymood))
|
self.send_irc.metadata("*", "set", "mood", str(mymood))
|
||||||
# Color via metadata
|
# Color via metadata
|
||||||
helpers.metadata(self.client, "*", "sub", "color")
|
self.send_irc.metadata("*", "sub", "color")
|
||||||
helpers.metadata(self.client, "*", "set", "color", str(color.name()))
|
self.send_irc.metadata("*", "set", "color", str(color.name()))
|
||||||
# Backwards compatible moods
|
# Backwards compatible moods
|
||||||
helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood))
|
self.send_irc.msg("#pesterchum", "MOOD >%d" % (mymood))
|
||||||
|
|
||||||
def erroneusnickname(self, *args):
|
def erroneusnickname(self, *args):
|
||||||
# Server is not allowing us to connect.
|
# Server is not allowing us to connect.
|
||||||
|
@ -702,7 +904,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
# No point in GETMOOD-ing services
|
# No point in GETMOOD-ing services
|
||||||
if failed_handle.casefold() not in SERVICES:
|
if failed_handle.casefold() not in SERVICES:
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", f"GETMOOD {failed_handle}")
|
self.send_irc.msg("#pesterchum", f"GETMOOD {failed_handle}")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
@ -712,7 +914,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
PchumLog.info("nomatchingkey: " + failed_handle)
|
PchumLog.info("nomatchingkey: " + failed_handle)
|
||||||
chumglub = "GETMOOD "
|
chumglub = "GETMOOD "
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", chumglub + failed_handle)
|
self.send_irc.msg("#pesterchum", chumglub + failed_handle)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
@ -722,7 +924,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
PchumLog.info("nomatchingkey: " + failed_handle)
|
PchumLog.info("nomatchingkey: " + failed_handle)
|
||||||
chumglub = "GETMOOD "
|
chumglub = "GETMOOD "
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", chumglub + failed_handle)
|
self.send_irc.msg("#pesterchum", chumglub + failed_handle)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
@ -744,12 +946,12 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
|
|
||||||
def nicknameinuse(self, server, cmd, nick, msg):
|
def nicknameinuse(self, server, cmd, nick, msg):
|
||||||
newnick = "pesterClient%d" % (random.randint(100, 999))
|
newnick = "pesterClient%d" % (random.randint(100, 999))
|
||||||
helpers.nick(self.client, newnick)
|
self.send_irc.nick(newnick)
|
||||||
self.parent.nickCollision.emit(nick, newnick)
|
self.parent.nickCollision.emit(nick, newnick)
|
||||||
|
|
||||||
def nickcollision(self, server, cmd, nick, msg):
|
def nickcollision(self, server, cmd, nick, msg):
|
||||||
newnick = "pesterClient%d" % (random.randint(100, 999))
|
newnick = "pesterClient%d" % (random.randint(100, 999))
|
||||||
helpers.nick(self.client, newnick)
|
self.send_irc.nick(newnick)
|
||||||
self.parent.nickCollision.emit(nick, newnick)
|
self.parent.nickCollision.emit(nick, newnick)
|
||||||
|
|
||||||
def quit(self, nick, reason):
|
def quit(self, nick, reason):
|
||||||
|
@ -953,7 +1155,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
# if nick_it in self.parent.mainwindow.namesdb["#pesterchum"]:
|
# if nick_it in self.parent.mainwindow.namesdb["#pesterchum"]:
|
||||||
# getglub += nick_it
|
# getglub += nick_it
|
||||||
# if getglub != "GETMOOD ":
|
# if getglub != "GETMOOD ":
|
||||||
# helpers.msg(self.client, "#pesterchum", getglub)
|
# self.send_irc.msg("#pesterchum", getglub)
|
||||||
|
|
||||||
def endofnames(self, server, nick, channel, msg):
|
def endofnames(self, server, nick, channel, msg):
|
||||||
try:
|
try:
|
||||||
|
@ -1031,9 +1233,9 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
self.parent.forbiddenchannel.emit(channel, msg)
|
self.parent.forbiddenchannel.emit(channel, msg)
|
||||||
self.parent.userPresentUpdate.emit(handle, channel, "left")
|
self.parent.userPresentUpdate.emit(handle, channel, "left")
|
||||||
|
|
||||||
def ping(self, prefix, server):
|
def ping(self, prefix, token):
|
||||||
# self.parent.mainwindow.lastping = time.time()
|
"""Respond to server PING with PONG."""
|
||||||
self.client.send("PONG", server)
|
self.send_irc.pong(token)
|
||||||
|
|
||||||
def getMood(self, *chums):
|
def getMood(self, *chums):
|
||||||
"""Get mood via metadata if supported"""
|
"""Get mood via metadata if supported"""
|
||||||
|
@ -1043,7 +1245,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
# Metadata
|
# Metadata
|
||||||
for chum in chums:
|
for chum in chums:
|
||||||
try:
|
try:
|
||||||
helpers.metadata(self.client, chum.handle, "get", "mood")
|
self.send_irc.metadata(chum.handle, "get", "mood")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
@ -1056,7 +1258,7 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
for chum in chums:
|
for chum in chums:
|
||||||
if len(chumglub + chum.handle) >= 350:
|
if len(chumglub + chum.handle) >= 350:
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", chumglub)
|
self.send_irc.msg("#pesterchum", chumglub)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
@ -1066,24 +1268,97 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
chumglub += chum.handle
|
chumglub += chum.handle
|
||||||
if chumglub != "GETMOOD ":
|
if chumglub != "GETMOOD ":
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", chumglub)
|
self.send_irc.msg("#pesterchum", chumglub)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
PchumLog.warning(e)
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
|
||||||
# def isOn(self, *chums):
|
def get(self, in_command_parts):
|
||||||
# isonNicks = ""
|
PchumLog.debug("in_command_parts: %s" % in_command_parts)
|
||||||
# for c in chums:
|
""" finds a command
|
||||||
# chandle = c.handle
|
commands may be dotted. each command part is checked that it does
|
||||||
# if len(chandle) >= 200:
|
not start with and underscore and does not have an attribute
|
||||||
# try:
|
"protected". if either of these is true, ProtectedCommandError
|
||||||
# self.client.send("ISON", ":%s" % (isonNicks))
|
is raised.
|
||||||
# except OSError:
|
its possible to pass both "command.sub.func" and
|
||||||
# self.parent.setConnectionBroken()
|
["command", "sub", "func"].
|
||||||
# isonNicks = ""
|
"""
|
||||||
# isonNicks += " " + chandle
|
if isinstance(in_command_parts, (str, bytes)):
|
||||||
# if isonNicks != "":
|
in_command_parts = in_command_parts.split(".")
|
||||||
# try:
|
command_parts = in_command_parts[:]
|
||||||
# self.client.send("ISON", ":%s" % (isonNicks))
|
|
||||||
# except OSError:
|
p = self
|
||||||
# self.parent.setConnectionBroken()
|
while command_parts:
|
||||||
|
cmd = command_parts.pop(0)
|
||||||
|
if cmd.startswith("_"):
|
||||||
|
raise ProtectedCommandError(in_command_parts)
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = getattr(p, cmd)
|
||||||
|
except AttributeError:
|
||||||
|
raise NoSuchCommandError(in_command_parts)
|
||||||
|
|
||||||
|
if hasattr(f, "protected"):
|
||||||
|
raise ProtectedCommandError(in_command_parts)
|
||||||
|
|
||||||
|
#if isinstance(f, self) and command_parts:
|
||||||
|
if command_parts:
|
||||||
|
return f.get(command_parts)
|
||||||
|
p = f
|
||||||
|
|
||||||
|
return f
|
||||||
|
|
||||||
|
def run_command(self, command, *args):
|
||||||
|
"""finds and runs a command"""
|
||||||
|
arguments_str = ""
|
||||||
|
for x in args:
|
||||||
|
arguments_str += str(x) + " "
|
||||||
|
PchumLog.debug("processCommand {}({})".format(command, arguments_str.strip()))
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = self.get(command)
|
||||||
|
except NoSuchCommandError as e:
|
||||||
|
PchumLog.info(e)
|
||||||
|
self.__unhandled__(command, *args)
|
||||||
|
return
|
||||||
|
|
||||||
|
PchumLog.debug("f %s" % f)
|
||||||
|
|
||||||
|
try:
|
||||||
|
f(*args)
|
||||||
|
except TypeError as e:
|
||||||
|
PchumLog.info(
|
||||||
|
"Failed to pass command, did the server pass an unsupported paramater? "
|
||||||
|
+ str(e)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# logging.info("Failed to pass command, %s" % str(e))
|
||||||
|
PchumLog.exception("Failed to pass command")
|
||||||
|
|
||||||
|
def __unhandled__(self, cmd, *args):
|
||||||
|
"""The default handler for commands. Override this method to
|
||||||
|
apply custom behavior (example, printing) unhandled commands.
|
||||||
|
"""
|
||||||
|
PchumLog.debug("unhandled command {}({})".format(cmd, args))
|
||||||
|
|
||||||
|
moodUpdated = QtCore.pyqtSignal("QString", Mood)
|
||||||
|
colorUpdated = QtCore.pyqtSignal("QString", QtGui.QColor)
|
||||||
|
messageReceived = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
memoReceived = QtCore.pyqtSignal("QString", "QString", "QString")
|
||||||
|
noticeReceived = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
inviteReceived = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
timeCommand = QtCore.pyqtSignal("QString", "QString", "QString")
|
||||||
|
namesReceived = QtCore.pyqtSignal("QString", PesterList)
|
||||||
|
channelListReceived = QtCore.pyqtSignal(PesterList)
|
||||||
|
nickCollision = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
getSvsnickedOn = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
myHandleChanged = QtCore.pyqtSignal("QString")
|
||||||
|
chanInviteOnly = QtCore.pyqtSignal("QString")
|
||||||
|
modesUpdated = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
connected = QtCore.pyqtSignal()
|
||||||
|
askToConnect = QtCore.pyqtSignal(Exception)
|
||||||
|
userPresentUpdate = QtCore.pyqtSignal("QString", "QString", "QString")
|
||||||
|
cannotSendToChan = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
tooManyPeeps = QtCore.pyqtSignal()
|
||||||
|
quirkDisable = QtCore.pyqtSignal("QString", "QString", "QString")
|
||||||
|
forbiddenchannel = QtCore.pyqtSignal("QString", "QString")
|
||||||
|
|
|
@ -194,7 +194,7 @@ SYSTEM = [
|
||||||
"umount2",
|
"umount2",
|
||||||
"vhangup",
|
"vhangup",
|
||||||
]
|
]
|
||||||
CALL_BLACKLIST = SETUID + SYSTEM
|
CALL_BLACKLIST = SYSTEM # + SETUID
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Optional
|
# Optional
|
||||||
|
|
|
@ -44,6 +44,9 @@ def names(cli, *channels):
|
||||||
def channel_list(cli):
|
def channel_list(cli):
|
||||||
cli.send("LIST")
|
cli.send("LIST")
|
||||||
|
|
||||||
|
def ping(cli, token):
|
||||||
|
"""Why is this called ping when it pongs.."""
|
||||||
|
cli.send("PONG", token)
|
||||||
|
|
||||||
def kick(cli, handle, channel, reason=""):
|
def kick(cli, handle, channel, reason=""):
|
||||||
cli.send("KICK {} {} {}".format(channel, handle, reason))
|
cli.send("KICK {} {} {}".format(channel, handle, reason))
|
||||||
|
@ -94,12 +97,6 @@ def _makeMsgRandomFunc(choices):
|
||||||
|
|
||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
msgYes = _makeMsgRandomFunc(["yes", "alright", "ok"])
|
|
||||||
msgOK = _makeMsgRandomFunc(["ok", "done"])
|
|
||||||
msgNo = _makeMsgRandomFunc(["no", "no-way"])
|
|
||||||
|
|
||||||
|
|
||||||
def ns(cli, *args):
|
def ns(cli, *args):
|
||||||
msg(cli, "NickServ", " ".join(args))
|
msg(cli, "NickServ", " ".join(args))
|
||||||
|
|
||||||
|
@ -156,4 +153,4 @@ def _addNumerics():
|
||||||
setattr(m, name, numericcmd(num, name))
|
setattr(m, name, numericcmd(num, name))
|
||||||
|
|
||||||
|
|
||||||
_addNumerics()
|
#_addNumerics()
|
||||||
|
|
128
scripts/irc/outgoing.py
Normal file
128
scripts/irc/outgoing.py
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
"""Class and functions for sending outgoing IRC commands."""
|
||||||
|
import socket
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger("pchumLogger")
|
||||||
|
|
||||||
|
|
||||||
|
class send_irc:
|
||||||
|
"""Provides functions for outgoing IRC commands."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.socket = None # INET socket connected with server.
|
||||||
|
self._end = None # Keep track of if we're disconnected.
|
||||||
|
|
||||||
|
def send(self, *args: str, text=None):
|
||||||
|
"""Send a command to the IRC server.
|
||||||
|
|
||||||
|
Takes either a string or a list of strings.
|
||||||
|
The 'text' argument is for the final parameter, which can have spaces."""
|
||||||
|
# Return if disconnected
|
||||||
|
if not self.socket or self.socket.fileno() == -1:
|
||||||
|
log.error(f"Send attempted while disconnected, args: {args}, text: {text}.")
|
||||||
|
return
|
||||||
|
|
||||||
|
command = ""
|
||||||
|
# Convert command arguments to a single string if passed.
|
||||||
|
if args:
|
||||||
|
command += " ".join(args)
|
||||||
|
# If text is passed, add ':' to imply everything after it is one parameter.
|
||||||
|
if text:
|
||||||
|
command += f" :{text}"
|
||||||
|
# Add characters for end of line in IRC.
|
||||||
|
command += "\r\n"
|
||||||
|
# UTF-8 is the prefered encoding in 2023.
|
||||||
|
outgoing_bytes = command.encode(encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
try:
|
||||||
|
log.debug(f"Sending: {command}")
|
||||||
|
self.socket.send(outgoing_bytes)
|
||||||
|
except OSError:
|
||||||
|
log.exception(f"Error while sending: '{command.strip()}'")
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
|
def ping(self, token: str):
|
||||||
|
"""Send PING command to server to check for connectivity."""
|
||||||
|
self.send("PING", text=token)
|
||||||
|
|
||||||
|
def pong(self, token: str):
|
||||||
|
"""Send PONG command to reply to server PING."""
|
||||||
|
self.send("PONG", token)
|
||||||
|
|
||||||
|
def nick(self, nick):
|
||||||
|
"""Send USER command to communicate nick to server."""
|
||||||
|
self.send("NICK", nick)
|
||||||
|
|
||||||
|
def user(self, username, realname):
|
||||||
|
"""Send USER command to communicate username and realname to server."""
|
||||||
|
self.send("USER", username, "0", "*", text=realname)
|
||||||
|
|
||||||
|
def msg(self, target, text):
|
||||||
|
"""Send PRIVMSG command to send a message."""
|
||||||
|
for line in msg.split("\n"):
|
||||||
|
self.send("PRIVMSG", target, text=text)
|
||||||
|
|
||||||
|
def names(self, channel: str):
|
||||||
|
"""Send NAMES command to view channel members."""
|
||||||
|
self.send("NAMES", channel)
|
||||||
|
|
||||||
|
def kick(self, channel, user, reason=""):
|
||||||
|
"""Send KICK command to force user from channel."""
|
||||||
|
if reason:
|
||||||
|
self.send(f"KICK {channel} {user}", text=reason)
|
||||||
|
else:
|
||||||
|
self.send(f"KICK {channel} {user}")
|
||||||
|
|
||||||
|
def mode(self, target, modestring, mode_arguments=""):
|
||||||
|
"""Set or remove modes from target."""
|
||||||
|
outgoing_mode = " ".join([target, modestring, mode_arguments]).strip()
|
||||||
|
self.send("MODE", outgoing_mode)
|
||||||
|
|
||||||
|
def ctcp(self, target, command, msg=""):
|
||||||
|
"""Send Client-to-Client Protocol message."""
|
||||||
|
outgoing_ctcp = " ".join([command, msg]).strip() # Extra spaces break protocol, so strip.
|
||||||
|
self.msg(target, f"\x01{outgoing_ctcp}\x01")
|
||||||
|
|
||||||
|
def metadata(self, target: str, subcommand: str, *params: str):
|
||||||
|
# IRC metadata draft specification
|
||||||
|
# https://gist.github.com/k4bek4be/92c2937cefd49990fbebd001faf2b237
|
||||||
|
self.send("METADATA", target, subcommand, *params)
|
||||||
|
|
||||||
|
def cap(self, subcommand: str, *params: str):
|
||||||
|
# Capability Negotiation
|
||||||
|
# https://ircv3.net/specs/extensions/capability-negotiation.html
|
||||||
|
self.send("CAP", subcommand, *params)
|
||||||
|
|
||||||
|
def join(self, channel: str, key=""):
|
||||||
|
"""Send JOIN command to join a channel/memo.
|
||||||
|
|
||||||
|
Keys or joining multiple channels is possible in the specification, but unused."""
|
||||||
|
channel_and_key = " ".join([channel, key]).strip()
|
||||||
|
self.send("JOIN", channel_and_key)
|
||||||
|
|
||||||
|
def part(self, channel: str):
|
||||||
|
"""Send PART command to leave a channel/memo.
|
||||||
|
|
||||||
|
Providing a reason or leaving multiple channels is possible in the specification."""
|
||||||
|
self.send("PART", channel)
|
||||||
|
|
||||||
|
def notice(self, target: str, text: str):
|
||||||
|
"""Send a NOTICE to a user or channel."""
|
||||||
|
self.send("NOTICE", target, text=text)
|
||||||
|
|
||||||
|
def invite(self, nick: str, channel: str):
|
||||||
|
"""Send INVITE command to invite a user to a channel."""
|
||||||
|
self.send("INVITE", nick, channel)
|
||||||
|
|
||||||
|
def away(self, text=None):
|
||||||
|
"""AWAY command to mark client as away or no longer away.
|
||||||
|
|
||||||
|
No 'text' parameter means the client is no longer away."""
|
||||||
|
if text:
|
||||||
|
self.send("AWAY", text=text)
|
||||||
|
else:
|
||||||
|
self.send("AWAY")
|
||||||
|
|
||||||
|
def banana(self):
|
||||||
|
"""Do you want a banana? diz bananan 4 u"""
|
||||||
|
self.join("#banana_kingdom", key="Shoplift8723")
|
Loading…
Reference in a new issue