experimenting with sockets
This commit is contained in:
parent
72f6b32668
commit
c13c05f51d
2 changed files with 88 additions and 35 deletions
70
irc.py
70
irc.py
|
@ -78,7 +78,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
self.registeredIRC = True
|
self.registeredIRC = True
|
||||||
self.connected.emit()
|
self.connected.emit()
|
||||||
def setConnectionBroken(self):
|
def setConnectionBroken(self):
|
||||||
PchumLog.debug("setconnection broken")
|
PchumLog.critical("setconnection broken")
|
||||||
self.reconnectIRC()
|
self.reconnectIRC()
|
||||||
#self.brokenConnection = True
|
#self.brokenConnection = True
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
|
@ -99,7 +99,7 @@ class PesterIRC(QtCore.QThread):
|
||||||
return res
|
return res
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def reconnectIRC(self):
|
def reconnectIRC(self):
|
||||||
PchumLog.debug("reconnectIRC() from thread %s" % (self))
|
PchumLog.warning("reconnectIRC() from thread %s" % (self))
|
||||||
self.cli.close()
|
self.cli.close()
|
||||||
|
|
||||||
@QtCore.pyqtSlot(PesterProfile)
|
@QtCore.pyqtSlot(PesterProfile)
|
||||||
|
@ -114,7 +114,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
t = str(text)
|
t = str(text)
|
||||||
try:
|
try:
|
||||||
helpers.notice(self.cli, h, t)
|
helpers.notice(self.cli, h, t)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString, QString)
|
@QtCore.pyqtSlot(QString, QString)
|
||||||
def sendMessage(self, text, handle):
|
def sendMessage(self, text, handle):
|
||||||
|
@ -160,7 +161,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
try:
|
try:
|
||||||
for t in textl:
|
for t in textl:
|
||||||
helpers.msg(self.cli, h, t)
|
helpers.msg(self.cli, h, t)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString, bool)
|
@QtCore.pyqtSlot(QString, bool)
|
||||||
def startConvo(self, handle, initiated):
|
def startConvo(self, handle, initiated):
|
||||||
|
@ -169,14 +171,16 @@ class PesterIRC(QtCore.QThread):
|
||||||
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
|
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
|
||||||
if initiated:
|
if initiated:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:BEGIN")
|
helpers.msg(self.cli, h, "PESTERCHUM:BEGIN")
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def endConvo(self, handle):
|
def endConvo(self, handle):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:CEASE")
|
helpers.msg(self.cli, h, "PESTERCHUM:CEASE")
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def updateProfile(self):
|
def updateProfile(self):
|
||||||
|
@ -184,7 +188,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
handle = me.handle
|
handle = me.handle
|
||||||
try:
|
try:
|
||||||
helpers.nick(self.cli, handle)
|
helpers.nick(self.cli, handle)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
self.mainwindow.closeConversations(True)
|
self.mainwindow.closeConversations(True)
|
||||||
self.mainwindow.doAutoIdentify()
|
self.mainwindow.doAutoIdentify()
|
||||||
|
@ -196,7 +201,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
me = self.mainwindow.profile()
|
me = self.mainwindow.profile()
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, "#pesterchum", "MOOD >%d" % (me.mood.value()))
|
helpers.msg(self.cli, "#pesterchum", "MOOD >%d" % (me.mood.value()))
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def updateColor(self):
|
def updateColor(self):
|
||||||
|
@ -205,34 +211,39 @@ class PesterIRC(QtCore.QThread):
|
||||||
for h in list(self.mainwindow.convos.keys()):
|
for h in list(self.mainwindow.convos.keys()):
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
|
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def blockedChum(self, handle):
|
def blockedChum(self, handle):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:BLOCK")
|
helpers.msg(self.cli, h, "PESTERCHUM:BLOCK")
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def unblockedChum(self, handle):
|
def unblockedChum(self, handle):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.cli, h, "PESTERCHUM:UNBLOCK")
|
helpers.msg(self.cli, h, "PESTERCHUM:UNBLOCK")
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def requestNames(self, channel):
|
def requestNames(self, channel):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.names(self.cli, c)
|
helpers.names(self.cli, c)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def requestChannelList(self):
|
def requestChannelList(self):
|
||||||
try:
|
try:
|
||||||
helpers.channel_list(self.cli)
|
helpers.channel_list(self.cli)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def joinChannel(self, channel):
|
def joinChannel(self, channel):
|
||||||
|
@ -240,7 +251,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
try:
|
try:
|
||||||
helpers.join(self.cli, c)
|
helpers.join(self.cli, c)
|
||||||
helpers.mode(self.cli, c, "", None)
|
helpers.mode(self.cli, c, "", None)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def leftChannel(self, channel):
|
def leftChannel(self, channel):
|
||||||
|
@ -248,7 +260,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
try:
|
try:
|
||||||
helpers.part(self.cli, c)
|
helpers.part(self.cli, c)
|
||||||
self.cli.command_handler.joined = False
|
self.cli.command_handler.joined = False
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString, QString)
|
@QtCore.pyqtSlot(QString, QString)
|
||||||
def kickUser(self, handle, channel):
|
def kickUser(self, handle, channel):
|
||||||
|
@ -264,7 +277,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
reason = ""
|
reason = ""
|
||||||
try:
|
try:
|
||||||
helpers.kick(self.cli, h, c, reason)
|
helpers.kick(self.cli, h, c, reason)
|
||||||
except socket.error:
|
except socket.error as 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):
|
||||||
|
@ -276,14 +290,16 @@ class PesterIRC(QtCore.QThread):
|
||||||
cmd = None
|
cmd = None
|
||||||
try:
|
try:
|
||||||
helpers.mode(self.cli, c, m, cmd)
|
helpers.mode(self.cli, c, m, cmd)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString)
|
@QtCore.pyqtSlot(QString)
|
||||||
def channelNames(self, channel):
|
def channelNames(self, channel):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.names(self.cli, c)
|
helpers.names(self.cli, c)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
@QtCore.pyqtSlot(QString, QString)
|
@QtCore.pyqtSlot(QString, QString)
|
||||||
def inviteChum(self, handle, channel):
|
def inviteChum(self, handle, channel):
|
||||||
|
@ -291,14 +307,16 @@ class PesterIRC(QtCore.QThread):
|
||||||
c = str(channel)
|
c = str(channel)
|
||||||
try:
|
try:
|
||||||
helpers.invite(self.cli, h, c)
|
helpers.invite(self.cli, h, c)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def pingServer(self):
|
def pingServer(self):
|
||||||
try:
|
try:
|
||||||
self.cli.send("PING %s" % int(time()))
|
self.cli.send("PING %s" % int(time()))
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
|
||||||
@QtCore.pyqtSlot(bool)
|
@QtCore.pyqtSlot(bool)
|
||||||
|
@ -308,7 +326,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
self.cli.send("AWAY Idle")
|
self.cli.send("AWAY Idle")
|
||||||
else:
|
else:
|
||||||
self.cli.send("AWAY")
|
self.cli.send("AWAY")
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
|
||||||
@QtCore.pyqtSlot(QString, QString)
|
@QtCore.pyqtSlot(QString, QString)
|
||||||
|
@ -317,7 +336,8 @@ class PesterIRC(QtCore.QThread):
|
||||||
h = str(handle)
|
h = str(handle)
|
||||||
try:
|
try:
|
||||||
helpers.ctcp(self.cli, c, "NOQUIRKS", h)
|
helpers.ctcp(self.cli, c, "NOQUIRKS", h)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.setConnectionBroken()
|
self.setConnectionBroken()
|
||||||
|
|
||||||
def quit_dc(self):
|
def quit_dc(self):
|
||||||
|
@ -647,14 +667,16 @@ class PesterHandler(DefaultCommandHandler):
|
||||||
if len(chumglub+chandle) >= 350:
|
if len(chumglub+chandle) >= 350:
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", chumglub)
|
helpers.msg(self.client, "#pesterchum", chumglub)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
chumglub = "GETMOOD "
|
chumglub = "GETMOOD "
|
||||||
chumglub += chandle
|
chumglub += chandle
|
||||||
if chumglub != "GETMOOD ":
|
if chumglub != "GETMOOD ":
|
||||||
try:
|
try:
|
||||||
helpers.msg(self.client, "#pesterchum", chumglub)
|
helpers.msg(self.client, "#pesterchum", chumglub)
|
||||||
except socket.error:
|
except socket.error as e:
|
||||||
|
PchumLog.warning(e)
|
||||||
self.parent.setConnectionBroken()
|
self.parent.setConnectionBroken()
|
||||||
|
|
||||||
#def isOn(self, *chums):
|
#def isOn(self, *chums):
|
||||||
|
|
|
@ -30,6 +30,7 @@ import traceback
|
||||||
import ostools
|
import ostools
|
||||||
import ssl
|
import ssl
|
||||||
import json
|
import json
|
||||||
|
import select
|
||||||
_datadir = ostools.getDataDir()
|
_datadir = ostools.getDataDir()
|
||||||
|
|
||||||
from oyoyo.parse import *
|
from oyoyo.parse import *
|
||||||
|
@ -93,19 +94,22 @@ class IRCClient:
|
||||||
if TLS == False:
|
if TLS == False:
|
||||||
#print("false")
|
#print("false")
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
#self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
else:
|
else:
|
||||||
self.context = ssl.create_default_context()
|
self.context = ssl.create_default_context()
|
||||||
self.context.check_hostname = False
|
self.context.check_hostname = False
|
||||||
self.context.verify_mode = ssl.CERT_NONE
|
self.context.verify_mode = ssl.CERT_NONE
|
||||||
self.bare_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.bare_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.socket = self.context.wrap_socket(self.bare_socket)
|
self.socket = self.context.wrap_socket(self.bare_socket)
|
||||||
|
#self.socket.setsockopt(socket.IPPROTO_TCP, socket.SO_KEEPALIVE, 1)
|
||||||
|
self.blocking = True
|
||||||
|
self.socket.setblocking(self.blocking)
|
||||||
|
|
||||||
self.nick = None
|
self.nick = None
|
||||||
self.real_name = None
|
self.real_name = None
|
||||||
self.host = None
|
self.host = None
|
||||||
self.port = None
|
self.port = None
|
||||||
self.connect_cb = None
|
self.connect_cb = None
|
||||||
self.blocking = False
|
|
||||||
self.timeout = None
|
self.timeout = None
|
||||||
|
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
|
@ -138,15 +142,28 @@ class IRCClient:
|
||||||
elif type(arg).__name__ == 'unicode':
|
elif type(arg).__name__ == 'unicode':
|
||||||
bargs.append(arg.encode(encoding))
|
bargs.append(arg.encode(encoding))
|
||||||
else:
|
else:
|
||||||
|
PchumLog.warning('Refusing to send one of the args from provided: %s'% repr([(type(arg), arg) for arg in args]))
|
||||||
raise IRCClientError('Refusing to send one of the args from provided: %s'
|
raise IRCClientError('Refusing to send one of the args from provided: %s'
|
||||||
% repr([(type(arg), arg) for arg in args]))
|
% repr([(type(arg), arg) for arg in args]))
|
||||||
|
|
||||||
msg = bytes(" ", "UTF-8").join(bargs)
|
msg = bytes(" ", "UTF-8").join(bargs)
|
||||||
PchumLog.info('---> send "%s"' % msg)
|
PchumLog.info('---> send "%s"' % msg)
|
||||||
try:
|
try:
|
||||||
self.socket.sendall(msg + bytes("\r\n", "UTF-8"))
|
# Extra block to give a failed write another try, causes a reconnect otherwise
|
||||||
|
retry = 0
|
||||||
|
while retry < 5:
|
||||||
|
try:
|
||||||
|
ready_to_read, ready_to_write, in_error = select.select([], [self.socket], [])
|
||||||
|
PchumLog.debug("ready_to_write (len %s): " % len(ready_to_write) + str(ready_to_write))
|
||||||
|
#ready_to_write[0].sendall(msg + bytes("\r\n", "UTF-8"))
|
||||||
|
for x in ready_to_write:
|
||||||
|
x.sendall(msg + bytes("\r\n", "UTF-8"))
|
||||||
|
break
|
||||||
|
except socket.error as e:
|
||||||
|
retry += 1
|
||||||
|
PchumLog.warning("socket.error (retry %s) %s" % (retry, e))
|
||||||
except socket.error as se:
|
except socket.error as se:
|
||||||
PchumLog.debug("socket.error %s" % se)
|
PchumLog.warning("socket.error %s" % se)
|
||||||
try: # a little dance of compatibility to get the errno
|
try: # a little dance of compatibility to get the errno
|
||||||
errno = se.errno
|
errno = se.errno
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -178,17 +195,25 @@ class IRCClient:
|
||||||
buffer = bytes()
|
buffer = bytes()
|
||||||
while not self._end:
|
while not self._end:
|
||||||
try:
|
try:
|
||||||
buffer += self.socket.recv(1024)
|
ready_to_read, ready_to_write, in_error = select.select([self.socket], [], []) # Don't wanna cause an unnecessary timeout
|
||||||
|
# Though this could probably be 90
|
||||||
|
PchumLog.debug("ready_to_read (len %s): " % len(ready_to_read) + str(ready_to_read))
|
||||||
|
for x in ready_to_read:
|
||||||
|
buffer += x.recv(1024)
|
||||||
|
#print("pre-recv")
|
||||||
|
#buffer += self.socket.recv(1024)
|
||||||
|
#print("post-recv")
|
||||||
|
#print(buffer)
|
||||||
#raise socket.timeout
|
#raise socket.timeout
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
|
PchumLog.warning("timeout in client.py, " + e)
|
||||||
if self._end:
|
if self._end:
|
||||||
break
|
break
|
||||||
PchumLog.debug("timeout in client.py")
|
|
||||||
raise e
|
raise e
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
|
PchumLog.warning("conn socket.error %s in %s" % (e, self))
|
||||||
if self._end:
|
if self._end:
|
||||||
break
|
break
|
||||||
PchumLog.debug("error %s" % e)
|
|
||||||
try: # a little dance of compatibility to get the errno
|
try: # a little dance of compatibility to get the errno
|
||||||
errno = e.errno
|
errno = e.errno
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -200,7 +225,9 @@ class IRCClient:
|
||||||
else:
|
else:
|
||||||
if self._end:
|
if self._end:
|
||||||
break
|
break
|
||||||
|
PchumLog.debug("pre buffer check, len(buffer) = " + str(len(buffer)))
|
||||||
if len(buffer) == 0 and self.blocking:
|
if len(buffer) == 0 and self.blocking:
|
||||||
|
PchumLog.debug("len(buffer) = 0")
|
||||||
raise socket.error("Connection closed")
|
raise socket.error("Connection closed")
|
||||||
|
|
||||||
data = buffer.split(bytes("\n", "UTF-8"))
|
data = buffer.split(bytes("\n", "UTF-8"))
|
||||||
|
@ -209,6 +236,7 @@ class IRCClient:
|
||||||
PchumLog.debug("data = " + str(data))
|
PchumLog.debug("data = " + str(data))
|
||||||
|
|
||||||
for el in data:
|
for el in data:
|
||||||
|
PchumLog.debug("el=%s, data=%s" % (el,data))
|
||||||
prefix, command, args = parse_raw_irc_command(el)
|
prefix, command, args = parse_raw_irc_command(el)
|
||||||
try:
|
try:
|
||||||
self.command_handler.run(command, prefix, *args)
|
self.command_handler.run(command, prefix, *args)
|
||||||
|
@ -240,11 +268,14 @@ class IRCClient:
|
||||||
PchumLog.info('shutdown socket')
|
PchumLog.info('shutdown socket')
|
||||||
#print("shutdown socket")
|
#print("shutdown socket")
|
||||||
self._end = True
|
self._end = True
|
||||||
self.socket.shutdown(socket.SHUT_WR)
|
try:
|
||||||
self.socket.close()
|
self.socket.shutdown(SHUT_RDWR)
|
||||||
|
except OSError as e:
|
||||||
def simple_send(self, message):
|
PchumLog.warning("Error while shutting down socket, already broken? %s" % e)
|
||||||
self.socket.sendall(bytes(message, "UTF-8"))
|
try:
|
||||||
|
self.socket.close()
|
||||||
|
except OSError as e:
|
||||||
|
PchumLog.warning("Error while closing socket, already broken? %s" % e)
|
||||||
|
|
||||||
def quit(self, msg):
|
def quit(self, msg):
|
||||||
PchumLog.info("QUIT")
|
PchumLog.info("QUIT")
|
||||||
|
|
Loading…
Reference in a new issue