pesterchum/irc.py
2022-08-19 13:12:58 +02:00

920 lines
36 KiB
Python

import logging
import logging.config
import socket
import random
import time
import json
try:
from PyQt6 import QtCore, QtGui
except ImportError:
print("PyQt5 fallback (irc.py)")
from PyQt5 import QtCore, QtGui
import ostools
from mood import Mood
from dataobjs import PesterProfile
from generic import PesterList
from version import _pcVersion
from oyoyo.client import IRCClient
from oyoyo.cmdhandler import DefaultCommandHandler
from oyoyo import helpers, services
_datadir = ostools.getDataDir()
logging.config.fileConfig(_datadir + "logging.ini")
PchumLog = logging.getLogger('pchumLogger')
# Python 3
QString = str
# Copied from pesterchum.py
#CUSTOMBOTS = ["CALSPRITE", "RANDOMENCOUNTER"]
#BOTNAMES = ["NICKSERV", "CHANSERV", "MEMOSERV", "OPERSERV", "HELPSERV", "HOSTSERV", "BOTSERV"]
#BOTNAMES.extend(CUSTOMBOTS)
#if ostools.isOSXBundle():
# logging.basicConfig(level=logging.WARNING)
#else:
# # karxi; We do NOT need this set to INFO; it's very, very spammy.
# logging.basicConfig(level=logging.WARNING)
class PesterIRC(QtCore.QThread):
def __init__(self, config, window):
QtCore.QThread.__init__(self)
self.mainwindow = window
self.config = config
self.unresponsive = False
self.registeredIRC = False
self.metadata_supported = False
self.stopIRC = None
self.NickServ = services.NickServ()
self.ChanServ = services.ChanServ()
def IRCConnect(self):
self.cli = IRCClient(PesterHandler,
host=self.config.server(),
port=self.config.port(),
ssl=self.config.ssl(),
nick=self.mainwindow.profile().handle,
username='pcc31',
realname='pcc31',
timeout=120)
self.cli.command_handler.parent = self
self.cli.command_handler.mainwindow = self.mainwindow
self.cli.connect()
self.conn = self.cli.conn()
def run(self):
try:
self.IRCConnect()
except OSError as se:
self.stopIRC = se
return
while True:
res = True
try:
PchumLog.debug("updateIRC()")
self.mainwindow.sincerecv = 0
res = self.updateIRC()
except socket.timeout as se:
PchumLog.debug("timeout in thread %s" % (self))
self.cli.close()
self.stopIRC = "%s, %s" % (type(se), se)
return
except (OSError, IndexError, ValueError) as se:
self.stopIRC = "%s, %s" % (type(se), se)
PchumLog.debug("socket error, exiting thread")
return
else:
if not res:
PchumLog.debug("false Yield: %s, returning" % res)
return
def setConnected(self):
self.registeredIRC = True
self.connected.emit()
def setConnectionBroken(self):
PchumLog.critical("setconnection broken")
self.disconnectIRC()
#self.brokenConnection = True # Unused
@QtCore.pyqtSlot()
def updateIRC(self):
try:
res = next(self.conn)
except socket.timeout as se:
if self.registeredIRC:
return True
else:
raise se
except OSError as se:
raise se
except (OSError,
ValueError,
IndexError) as se:
raise se
except StopIteration:
self.conn = self.cli.conn()
return True
else:
return res
@QtCore.pyqtSlot(PesterProfile)
def getMood(self, *chums):
self.cli.command_handler.getMood(*chums)
@QtCore.pyqtSlot(PesterList)
def getMoods(self, chums):
self.cli.command_handler.getMood(*chums)
@QtCore.pyqtSlot(QString, QString)
def sendNotice(self, text, handle):
h = str(handle)
t = str(text)
try:
helpers.notice(self.cli, h, t)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, QString)
def sendMessage(self, text, handle):
h = str(handle)
textl = [str(text)]
def splittext(l):
if len(l[0]) > 450:
space = l[0].rfind(" ", 0,430)
if space == -1:
space = 450
elif l[0][space+1:space+5] == "</c>":
space = space+4
a = l[0][0:space+1]
b = l[0][space+1:]
if a.count("<c") > a.count("</c>"):
# oh god ctags will break!! D=
hanging = []
usedends = []
c = a.rfind("<c")
while c != -1:
d = a.find("</c>", c)
while d in usedends:
d = a.find("</c>", d+1)
if d != -1: usedends.append(d)
else:
f = a.find(">", c)+1
hanging.append(a[c:f])
c = a.rfind("<c",0,c)
# end all ctags in first part
for i in range(a.count("<c")-a.count("</c>")):
a = a + "</c>"
#start them up again in the second part
for c in hanging:
b = c + b
if len(b) > 0:
return [a] + splittext([b])
else:
return [a]
else:
return l
textl = splittext(textl)
try:
for t in textl:
helpers.msg(self.cli, h, t)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, QString,)
def sendCTCP(self, handle, text):
#cmd = text.split(' ')[0]
#msg = text.replace(cmd + ' ', '')
#msg = msg.replace(cmd, '')
try:
helpers.ctcp(self.cli, handle, text)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, bool)
def startConvo(self, handle, initiated):
h = str(handle)
try:
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
if initiated:
helpers.msg(self.cli, h, "PESTERCHUM:BEGIN")
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString)
def endConvo(self, handle):
h = str(handle)
try:
helpers.msg(self.cli, h, "PESTERCHUM:CEASE")
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot()
def updateProfile(self):
me = self.mainwindow.profile()
handle = me.handle
try:
helpers.nick(self.cli, handle)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
self.mainwindow.closeConversations(True)
self.mainwindow.doAutoIdentify()
self.mainwindow.autoJoinDone = False
self.mainwindow.doAutoJoins()
self.updateMood()
@QtCore.pyqtSlot()
def updateMood(self):
me = self.mainwindow.profile()
# Moods via metadata
try:
helpers.metadata(self.cli, '*', "set", "mood", str(me.mood.value()))
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
# Backwards compatibility
try:
helpers.msg(self.cli, "#pesterchum", "MOOD >%d" % (me.mood.value()))
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot()
def updateColor(self):
#PchumLog.debug("irc updateColor (outgoing)")
#me = self.mainwindow.profile()
for h in list(self.mainwindow.convos.keys()):
try:
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString)
def blockedChum(self, handle):
h = str(handle)
try:
helpers.msg(self.cli, h, "PESTERCHUM:BLOCK")
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString)
def unblockedChum(self, handle):
h = str(handle)
try:
helpers.msg(self.cli, h, "PESTERCHUM:UNBLOCK")
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString)
def requestNames(self, channel):
c = str(channel)
try:
helpers.names(self.cli, c)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot()
def requestChannelList(self):
try:
helpers.channel_list(self.cli)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString)
def joinChannel(self, channel):
c = str(channel)
try:
helpers.join(self.cli, c)
helpers.mode(self.cli, c, "", None)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString)
def leftChannel(self, channel):
c = str(channel)
try:
helpers.part(self.cli, c)
self.cli.command_handler.joined = False
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, QString)
def kickUser(self, handle, channel):
l = handle.split(":")
c = str(channel)
h = str(l[0])
if len(l) > 1:
reason = str(l[1])
if len(l) > 2:
for x in l[2:]:
reason += str(":") + str(x)
else:
reason = ""
try:
helpers.kick(self.cli, h, c, reason)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, QString, QString)
def setChannelMode(self, channel, mode, command):
c = str(channel)
m = str(mode)
cmd = str(command)
PchumLog.debug("c=%s\nm=%s\ncmd=%s" % (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)
def channelNames(self, channel):
c = str(channel)
try:
helpers.names(self.cli, c)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, QString)
def inviteChum(self, handle, channel):
h = str(handle)
c = str(channel)
try:
helpers.invite(self.cli, h, c)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot()
def pingServer(self):
try:
if hasattr(self, 'cli'):
self.cli.send("PING :B33")
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(bool)
def setAway(self, away=True):
try:
if away:
self.cli.send("AWAY Idle")
else:
self.cli.send("AWAY")
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot(QString, QString)
def killSomeQuirks(self, channel, handle):
c = str(channel)
h = str(handle)
try:
helpers.ctcp(self.cli, c, "NOQUIRKS", h)
except OSError as e:
PchumLog.warning(e)
self.setConnectionBroken()
@QtCore.pyqtSlot()
def disconnectIRC(self):
if hasattr(self, 'cli'):
helpers.quit(self.cli, _pcVersion + " <3")
self.cli._end = True
self.cli.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()
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):
handle = nick[0:nick.find("!")]
PchumLog.info("---> recv \"NOTICE %s :%s\"" % (handle, msg))
if handle == "ChanServ" and chan == self.parent.mainwindow.profile().handle and msg[0:2] == "[#":
self.parent.memoReceived.emit(msg[1:msg.index("]")], handle, msg)
else:
self.parent.noticeReceived.emit(handle, msg)
def metadata(self, target, nick, key, visibility, value):
# The format of the METADATA server notication is:
# METADATA <Target> <Key> <Visibility> <Value>
if key == "mood":
mood = Mood(int(value))
self.parent.moodUpdated.emit(nick, mood)
def tagmsg(self, prefix, tags, *args):
PchumLog.info('TAGMSG: %s %s %s' % (prefix, tags, str(args)))
message_tags = tags[1:].split(';')
for m in message_tags:
if m.startswith("+pesterchum"):
# Pesterchum tag
try:
key, value = m.split('=')
except ValueError:
return
PchumLog.info('Pesterchum tag: %s=%s' % (key, value))
# PESTERCHUM: syntax check
if ((value == "BEGIN")
or (value == "BLOCK")
or (value == "CEASE")
or (value == "BLOCK")
or (value == "BLOCKED")
or (value == "UNBLOCK")
or (value == "IDLE")
or (value == "ME")):
# Process like it's a PESTERCHUM: PRIVMSG
msg = "PESTERCHUM:" + value
self.privmsg(prefix, args[0], msg)
elif value.startswith("COLOR>"):
# Process like it's a COLOR >0,0,0 PRIVMSG
msg = value.replace('>', " >")
self.privmsg(prefix, args[0], msg)
elif value.startswith("TIME>"):
# Process like it's a PESTERCHUM:TIME> PRIVMSG
msg = "PESTERCHUM:" + value
self.privmsg(prefix, args[0], msg)
else:
# Invalid syntax
PchumLog.warning("TAGMSG with invalid syntax.")
def error(self, *params):
# Server is ending connection.
reason = ''
for x in params:
if (x != None) and (x != ''):
reason += x + ' '
self.parent.stopIRC = reason.strip()
self.parent.disconnectIRC()
def privmsg(self, nick, chan, msg):
handle = nick[0:nick.find("!")]
if len(msg) == 0:
return
# CTCP
# ACTION, IRC /me (The CTCP kind)
if msg[0:8] == '\x01ACTION ':
msg = '/me' + msg[7:-1]
# CTCPs that don't need to be shown
elif msg[0] == '\x01':
PchumLog.info("---> recv \"CTCP %s :%s\"" % (handle, msg[1:-1]))
# VERSION, return version
if msg[1:-1].startswith("VERSION"):
helpers.ctcp_reply(self.parent.cli, handle, "VERSION", "Pesterchum %s" % (_pcVersion))
# CLIENTINFO, return supported CTCP commands.
elif msg[1:-1].startswith("CLIENTINFO"):
helpers.ctcp_reply(self.parent.cli, handle, "CLIENTINFO",
"ACTION VERSION CLIENTINFO PING SOURCE NOQUIRKS GETMOOD")
# PING, return pong
elif msg[1:-1].startswith("PING"):
if len(msg[1:-1].split("PING ")) > 1:
helpers.ctcp_reply(self.parent.cli, handle, "PING", msg[1:-1].split("PING ")[1])
else:
helpers.ctcp_reply(self.parent.cli, handle, "PING")
# SOURCE, return source
elif msg[1:-1].startswith("SOURCE"):
helpers.ctcp_reply(self.parent.cli, handle, "SOURCE", "https://github.com/Dpeta/pesterchum-alt-servers")
# ???
elif msg[1:-1].startswith("NOQUIRKS") and chan[0] == "#":
op = nick[0:nick.find("!")]
self.parent.quirkDisable.emit(chan, msg[10:-1], op)
# GETMOOD via CTCP
elif msg[1:-1].startswith("GETMOOD"):
# GETMOOD via CTCP
# Maybe we can do moods like this in the future...
mymood = self.mainwindow.profile().mood.value()
helpers.ctcp_reply(self.parent.cli, handle, "MOOD >%d" % (mymood))
# Backwards compatibility
helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood))
return
if chan != "#pesterchum":
# We don't need anywhere near that much spam.
PchumLog.info("---> recv \"PRIVMSG %s :%s\"" % (handle, msg))
if chan == "#pesterchum":
# follow instructions
if msg[0:6] == "MOOD >":
try:
mood = Mood(int(msg[6:]))
except ValueError:
mood = Mood(0)
self.parent.moodUpdated.emit(handle, mood)
elif msg[0:7] == "GETMOOD":
mychumhandle = self.mainwindow.profile().handle
mymood = self.mainwindow.profile().mood.value()
if msg.find(mychumhandle, 8) != -1:
helpers.msg(self.client, "#pesterchum",
"MOOD >%d" % (mymood))
elif chan[0] == '#':
if msg[0:16] == "PESTERCHUM:TIME>":
self.parent.timeCommand.emit(chan, handle, msg[16:])
else:
self.parent.memoReceived.emit(chan, handle, msg)
else:
# private message
# silently ignore messages to yourself.
if handle == self.mainwindow.profile().handle:
return
if msg[0:7] == "COLOR >":
colors = msg[7:].split(",")
try:
colors = [int(d) for d in colors]
except ValueError as e:
PchumLog.warning(e)
colors = [0,0,0]
PchumLog.debug("colors: " + str(colors))
color = QtGui.QColor(*colors)
self.parent.colorUpdated.emit(handle, color)
else:
self.parent.messageReceived.emit(handle, msg)
def pong(self, *args):
# source, server, token
#print("PONG", source, server, token)
#self.parent.mainwindow.lastrecv = time.time()
#print("PONG TIME: %s" % self.parent.mainwindow.lastpong)
pass
def welcome(self, server, nick, msg):
self.parent.setConnected()
#mychumhandle = self.mainwindow.profile().handle
mymood = self.mainwindow.profile().mood.value()
if not self.mainwindow.config.lowBandwidth():
time.sleep(0.413 + 0.097) # <--- somehow, this actually helps.
helpers.join(self.client, "#pesterchum")
# Moods via metadata
helpers.metadata(self.client, '*', 'sub', 'mood')
helpers.metadata(self.client, '*', "set", "mood", str(mymood))
# Backwards compatible moods
helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood))
# Negotiate Pesterchum message tags
helpers.cap(self.client, "REQ", "message-tags")
helpers.cap(self.client, "REQ", "pesterchum-tag")
def erroneusnickname(self, *args):
# Server is not allowing us to connect.
reason = "Handle is not allowed on this server.\n"
for x in args:
if (x != None) and (x != ''):
reason += x + ' '
self.parent.stopIRC = reason.strip()
self.parent.disconnectIRC()
def keyvalue(self, target, handle_us, handle_owner, key, visibility, *value):
# The format of the METADATA server notication is:
# METADATA <Target> <Key> <Visibility> <Value>
if key == "mood":
mood = Mood(int(value[0]))
self.parent.moodUpdated.emit(handle_owner, mood)
def metadatasubok(self, *params):
PchumLog.info("metadatasubok: " + str(params))
def nomatchingkey(self, target, our_handle, failed_handle, key, *error):
# Try to get moods the old way if metadata fails.
PchumLog.info("nomatchingkey: " + failed_handle)
chumglub = "GETMOOD "
try:
helpers.msg(self.client, "#pesterchum", chumglub + failed_handle)
except OSError as e:
PchumLog.warning(e)
self.parent.setConnectionBroken()
def keynotset(self, target, our_handle, failed_handle, key, *error):
# Try to get moods the old way if metadata fails.
PchumLog.info("nomatchingkey: " + failed_handle)
chumglub = "GETMOOD "
try:
helpers.msg(self.client, "#pesterchum", chumglub + failed_handle)
except OSError as e:
PchumLog.warning(e)
self.parent.setConnectionBroken()
def keynopermission(self, target, our_handle, failed_handle, key, *error):
# Try to get moods the old way if metadata fails.
PchumLog.info("nomatchingkey: " + failed_handle)
chumglub = "GETMOOD "
try:
helpers.msg(self.client, "#pesterchum", chumglub + failed_handle)
except OSError as e:
PchumLog.warning(e)
self.parent.setConnectionBroken()
def featurelist(self, target, handle, *params):
# Better to do this via CAP ACK/CAP NEK
# RPL_ISUPPORT
features = params[:-1]
PchumLog.info("Server featurelist: " + str(features))
for x in features:
if x.upper().startswith("METADATA"):
PchumLog.info("Server supports metadata.")
self.parent.metadata_supported = True
def cap(self, server, nick, subcommand, tag):
PchumLog.info("CAP %s %s %s %s" % (server, nick, subcommand, tag))
#if tag == "message-tags":
# if subcommand == "ACK":
def nicknameinuse(self, server, cmd, nick, msg):
newnick = "pesterClient%d" % (random.randint(100,999))
helpers.nick(self.client, newnick)
self.parent.nickCollision.emit(nick, newnick)
def nickcollision(self, server, cmd, nick, msg):
newnick = "pesterClient%d" % (random.randint(100,999))
helpers.nick(self.client, newnick)
self.parent.nickCollision.emit(nick, newnick)
def quit(self, nick, reason):
handle = nick[0:nick.find("!")]
PchumLog.info("---> recv \"QUIT %s: %s\"" % (handle, reason))
if handle == self.parent.mainwindow.randhandler.randNick:
self.parent.mainwindow.randhandler.setRunning(False)
server = self.parent.mainwindow.config.server()
baseserver = server[server.rfind(".", 0, server.rfind(".")):]
if reason.count(baseserver) == 2:
self.parent.userPresentUpdate.emit(handle, "", "netsplit")
else:
self.parent.userPresentUpdate.emit(handle, "", "quit")
self.parent.moodUpdated.emit(handle, Mood("offline"))
def kick(self, opnick, channel, handle, reason):
op = opnick[0:opnick.find("!")]
self.parent.userPresentUpdate.emit(handle, channel, "kick:%s:%s" % (op, reason))
# ok i shouldnt be overloading that but am lazy
def part(self, nick, channel, reason="nanchos"):
handle = nick[0:nick.find("!")]
PchumLog.info("---> recv \"PART %s: %s\"" % (handle, channel))
self.parent.userPresentUpdate.emit(handle, channel, "left")
if channel == "#pesterchum":
self.parent.moodUpdated.emit(handle, Mood("offline"))
def join(self, nick, channel):
handle = nick[0:nick.find("!")]
PchumLog.info("---> recv \"JOIN %s: %s\"" % (handle, channel))
self.parent.userPresentUpdate.emit(handle, channel, "join")
if channel == "#pesterchum":
if handle == self.parent.mainwindow.randhandler.randNick:
self.parent.mainwindow.randhandler.setRunning(True)
self.parent.moodUpdated.emit(handle, Mood("chummy"))
def mode(self, op, channel, mode, *handles):
PchumLog.debug("op=" + str(op))
PchumLog.debug("channel=" + str(channel))
PchumLog.debug("mode=" + str(mode))
PchumLog.debug("*handles=" + str(handles))
if len(handles) <= 0: handles = [""]
opnick = op[0:op.find("!")]
PchumLog.debug("opnick=" + opnick)
# Channel section
# Okay so, as I understand it channel modes will always be applied to a channel even if the commands also sets a mode to a user.
# So "MODE #channel +ro handleHandle" will set +r to channel #channel as well as set +o to handleHandle
# Therefore the bellow method causes a crash if both user and channel mode are being set in one command.
#if op == channel or channel == self.parent.mainwindow.profile().handle:
# modes = list(self.parent.mainwindow.modes)
# if modes and modes[0] == "+": modes = modes[1:]
# if mode[0] == "+":
# for m in mode[1:]:
# if m not in modes:
# modes.extend(m)
# elif mode[0] == "-":
# for i in mode[1:]:
# try:
# modes.remove(i)
# except ValueError:
# pass
# modes.sort()
# self.parent.mainwindow.modes = "+" + "".join(modes)
# EXPIRIMENTAL FIX
# No clue how stable this is but since it doesn't seem to cause a crash it's probably an improvement.
# This might be clunky with non-unrealircd IRC servers
channel_mode = ""
unrealircd_channel_modes = ['c', 'C', 'd', 'f', 'G', 'H', 'i', 'k', 'K', 'L', 'l', 'm', 'M', 'N', 'n', 'O', 'P', 'p', 'Q', 'R', 'r', 's', 'S', 'T', 't', 'V', 'z', 'Z']
if any(md in mode for md in unrealircd_channel_modes):
PchumLog.debug("Channel mode in string.")
modes = list(self.parent.mainwindow.modes)
for md in unrealircd_channel_modes:
if mode.find(md)!= -1: # -1 means not found
PchumLog.debug("md=" + md)
if mode[0] == "+":
modes.extend(md)
channel_mode = "+" + md
elif mode[0] == "-":
try:
modes.remove(md)
channel_mode = "-" + md
except ValueError:
PchumLog.warning("Can't remove channel mode that isn't set.")
pass
self.parent.userPresentUpdate.emit("", channel, channel_mode+":%s" % (op))
PchumLog.debug("pre-mode=" + str(mode))
mode = mode.replace(md, "")
PchumLog.debug("post-mode=" + str(mode))
modes.sort()
self.parent.mainwindow.modes = "+" + "".join(modes)
modes = []
cur = "+"
for l in mode:
if l in ["+","-"]: cur = l
else:
modes.append("%s%s" % (cur, l))
PchumLog.debug("handles=" + str(handles))
PchumLog.debug("enumerate(modes) = " + str(list(enumerate(modes))))
for (i,m) in enumerate(modes):
# Server-set usermodes don't need to be passed.
if (handles == ['']) & ( ('x' in m) | ('z' in m) | ('o' in m) | ('x' in m) )!=True:
try:
self.parent.userPresentUpdate.emit(handles[i], channel, m+":%s" % (op))
except IndexError as e:
PchumLog.exception("modeSetIndexError: %s" % e)
#print("i = " + i)
#print("m = " + m)
#self.parent.userPresentUpdate.emit(handles[i], channel, m+":%s" % (op))
#self.parent.userPresentUpdate.emit(handles[i], channel, m+":%s" % (op))
# Passing an empty handle here might cause a crash.
#except IndexError:
#self.parent.userPresentUpdate.emit("", channel, m+":%s" % (op))
def nick(self, oldnick, newnick, hopcount=0):
PchumLog.info("%s, %s" % (oldnick, newnick))
# svsnick
if oldnick == self.mainwindow.profile().handle:
# Server changed our handle, svsnick?
self.parent.getSvsnickedOn.emit(oldnick, newnick)
# etc.
oldhandle = oldnick[0:oldnick.find("!")]
if ((oldhandle == self.mainwindow.profile().handle)
or (newnick == self.mainwindow.profile().handle)):
#print('hewwo')
self.parent.myHandleChanged.emit(newnick)
newchum = PesterProfile(newnick, chumdb=self.mainwindow.chumdb)
self.parent.moodUpdated.emit(oldhandle, Mood("offline"))
self.parent.userPresentUpdate.emit("%s:%s" % (oldhandle, newnick), "", "nick")
if newnick in self.mainwindow.chumList.chums:
self.getMood(newchum)
if oldhandle == self.parent.mainwindow.randhandler.randNick:
self.parent.mainwindow.randhandler.setRunning(False)
elif newnick == self.parent.mainwindow.randhandler.randNick:
self.parent.mainwindow.randhandler.setRunning(True)
def namreply(self, server, nick, op, channel, names):
namelist = names.split(" ")
PchumLog.info("---> recv \"NAMES %s: %d names\"" % (channel, len(namelist)))
if not hasattr(self, 'channelnames'):
self.channelnames = {}
if channel not in self.channelnames:
self.channelnames[channel] = []
self.channelnames[channel].extend(namelist)
#def ison(self, server, nick, nicks):
# nicklist = nicks.split(" ")
# getglub = "GETMOOD "
# PchumLog.info("---> recv \"ISON :%s\"" % nicks)
# for nick_it in nicklist:
# self.parent.moodUpdated.emit(nick_it, Mood(0))
# if nick_it in self.parent.mainwindow.namesdb["#pesterchum"]:
# getglub += nick_it
# if getglub != "GETMOOD ":
# helpers.msg(self.client, "#pesterchum", getglub)
def endofnames(self, server, nick, channel, msg):
try:
namelist = self.channelnames[channel]
except KeyError:
# EON seems to return with wrong capitalization sometimes?
for cn in self.channelnames.keys():
if channel.lower() == cn.lower():
channel = cn
namelist = self.channelnames[channel]
pl = PesterList(namelist)
del self.channelnames[channel]
self.parent.namesReceived.emit(channel, pl)
if channel == "#pesterchum" and (not hasattr(self, "joined") or not self.joined):
self.joined = True
self.parent.mainwindow.randhandler.setRunning(self.parent.mainwindow.randhandler.randNick in namelist)
chums = self.mainwindow.chumList.chums
#self.isOn(*chums)
lesschums = []
for c in chums:
chandle = c.handle
if chandle in namelist:
lesschums.append(c)
self.getMood(*lesschums)
def liststart(self, server, handle, *info):
self.channel_list = []
info = list(info)
self.channel_field = info.index("Channel") # dunno if this is protocol
PchumLog.info("---> recv \"CHANNELS: %s " % (self.channel_field))
def list(self, server, handle, *info):
channel = info[self.channel_field]
usercount = info[1]
if channel not in self.channel_list and channel != "#pesterchum":
self.channel_list.append((channel, usercount))
PchumLog.info("---> recv \"CHANNELS: %s " % (channel))
def listend(self, server, handle, msg):
pl = PesterList(self.channel_list)
PchumLog.info("---> recv \"CHANNELS END\"")
self.parent.channelListReceived.emit(pl)
self.channel_list = []
def umodeis(self, server, handle, modes):
self.parent.mainwindow.modes = modes
def invite(self, sender, you, channel):
handle = sender.split('!')[0]
self.parent.inviteReceived.emit(handle, channel)
def inviteonlychan(self, server, handle, channel, msg):
self.parent.chanInviteOnly.emit(channel)
# channelmodeis can have six arguments.
def channelmodeis(self, server, handle, channel, modes, mode_params=""):
self.parent.modesUpdated.emit(channel, modes)
def cannotsendtochan(self, server, handle, channel, msg):
self.parent.cannotSendToChan.emit(channel, msg)
def toomanypeeps(self, *stuff):
self.parent.tooManyPeeps.emit()
#def badchanmask(channel, *args):
# # Channel name is not valid.
# msg = ' '.join(args)
# self.parent.forbiddenchannel.emit(channel, msg)
def forbiddenchannel(self, server, handle, channel, msg):
# Channel is forbidden.
self.parent.forbiddenchannel.emit(channel, msg)
self.parent.userPresentUpdate.emit(handle, channel, "left")
def ping(self, prefix, server):
#self.parent.mainwindow.lastping = time.time()
self.client.send('PONG', server)
def getMood(self, *chums):
"""Get mood via metadata if supported"""
# Get via metadata or via legacy method
if self.parent.metadata_supported == True:
# Metadata
for c in chums:
chandle = c.handle
try:
helpers.metadata(self.client, chandle, "get", "mood")
except OSError as e:
PchumLog.warning(e)
self.parent.setConnectionBroken()
else:
# Legacy
PchumLog.warning("Server doesn't seem to support metadata, using legacy GETMOOD.")
chumglub = "GETMOOD "
for c in chums:
chandle = c.handle
if len(chumglub+chandle) >= 350:
try:
helpers.msg(self.client, "#pesterchum", chumglub)
except OSError as e:
PchumLog.warning(e)
self.parent.setConnectionBroken()
chumglub = "GETMOOD "
chumglub += chandle
if chumglub != "GETMOOD ":
try:
helpers.msg(self.client, "#pesterchum", chumglub)
except OSError as e:
PchumLog.warning(e)
self.parent.setConnectionBroken()
#def isOn(self, *chums):
# isonNicks = ""
# for c in chums:
# chandle = c.handle
# if len(chandle) >= 200:
# try:
# self.client.send("ISON", ":%s" % (isonNicks))
# except OSError:
# self.parent.setConnectionBroken()
# isonNicks = ""
# isonNicks += " " + chandle
# if isonNicks != "":
# try:
# self.client.send("ISON", ":%s" % (isonNicks))
# except OSError:
# self.parent.setConnectionBroken()