pesterchum/irc.py

541 lines
20 KiB
Python
Raw Normal View History

from PyQt4 import QtGui, QtCore
2011-02-15 12:10:57 -05:00
from oyoyo.client import IRCClient
from oyoyo.cmdhandler import DefaultCommandHandler
from oyoyo import helpers
import logging
2011-02-06 01:02:39 -05:00
import random
2011-02-15 12:10:57 -05:00
import socket
from time import time
from dataobjs import Mood, PesterProfile
from generic import PesterList
2011-05-26 03:40:30 -04:00
from version import _pcVersion
import sys, os
if sys.platform == "darwin" and os.path.abspath('.').find('.app') != -1:
logging.basicConfig(level=logging.WARNING)
else:
logging.basicConfig(level=logging.INFO)
2011-02-19 18:06:54 -05:00
class PesterIRC(QtCore.QThread):
2011-02-13 21:01:58 -05:00
def __init__(self, config, window):
2011-02-19 18:06:54 -05:00
QtCore.QThread.__init__(self)
self.mainwindow = window
2011-02-13 21:01:58 -05:00
self.config = config
2011-02-21 14:07:59 -05:00
self.registeredIRC = False
self.stopIRC = None
def IRCConnect(self):
2011-02-13 21:01:58 -05:00
server = self.config.server()
2011-02-15 12:10:57 -05:00
port = self.config.port()
2011-07-13 03:27:32 -04:00
self.cli = IRCClient(PesterHandler, host=server, port=int(port), nick=self.mainwindow.profile().handle, real_name='pcc31', blocking=True, timeout=15)
2011-02-15 12:10:57 -05:00
self.cli.command_handler.parent = self
self.cli.command_handler.mainwindow = self.mainwindow
2011-02-21 14:07:59 -05:00
self.cli.connect()
self.conn = self.cli.conn()
2011-02-19 18:06:54 -05:00
def run(self):
2011-02-21 14:07:59 -05:00
try:
self.IRCConnect()
except socket.error, se:
self.stopIRC = se
return
2011-02-19 18:06:54 -05:00
while 1:
2011-02-21 14:07:59 -05:00
res = True
2011-02-19 18:06:54 -05:00
try:
2011-02-21 14:07:59 -05:00
logging.debug("updateIRC()")
2011-02-19 21:38:06 -05:00
res = self.updateIRC()
except socket.timeout, se:
2011-02-21 14:07:59 -05:00
logging.debug("timeout in thread %s" % (self))
2011-02-19 21:38:06 -05:00
self.cli.close()
self.stopIRC = se
return
2011-02-19 18:06:54 -05:00
except socket.error, se:
if self.registeredIRC:
self.stopIRC = None
else:
self.stopIRC = se
2011-02-21 14:07:59 -05:00
logging.debug("socket error, exiting thread")
2011-02-19 18:06:54 -05:00
return
2011-02-19 21:38:06 -05:00
else:
if not res:
2011-02-21 14:07:59 -05:00
logging.debug("false Yield: %s, returning" % res)
2011-02-19 21:38:06 -05:00
return
2011-02-18 03:17:13 -05:00
def setConnected(self):
2011-02-18 21:02:54 -05:00
self.registeredIRC = True
2011-02-18 03:17:13 -05:00
self.connected.emit()
2011-02-19 18:06:54 -05:00
def setConnectionBroken(self):
2011-02-21 14:07:59 -05:00
logging.debug("setconnection broken")
2011-02-19 18:06:54 -05:00
self.reconnectIRC()
#self.brokenConnection = True
@QtCore.pyqtSlot()
def updateIRC(self):
try:
res = self.conn.next()
except socket.timeout, se:
if self.registeredIRC:
2011-02-19 21:38:06 -05:00
return True
2011-02-19 18:06:54 -05:00
else:
raise se
except socket.error, se:
raise se
except StopIteration:
2011-02-21 14:07:59 -05:00
self.conn = self.cli.conn()
2011-02-19 21:38:06 -05:00
return True
else:
return res
2011-02-19 18:06:54 -05:00
@QtCore.pyqtSlot()
def reconnectIRC(self):
2011-02-21 14:07:59 -05:00
logging.debug("reconnectIRC() from thread %s" % (self))
2011-02-19 18:06:54 -05:00
self.cli.close()
@QtCore.pyqtSlot(PesterProfile)
def getMood(self, *chums):
2011-02-15 12:10:57 -05:00
self.cli.command_handler.getMood(*chums)
@QtCore.pyqtSlot(PesterList)
def getMoods(self, chums):
self.cli.command_handler.getMood(*chums)
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def sendNotice(self, text, handle):
h = unicode(handle)
t = unicode(text)
try:
helpers.notice(self.cli, h, t)
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def sendMessage(self, text, handle):
h = unicode(handle)
2011-02-13 20:32:02 -05:00
textl = [unicode(text)]
def splittext(l):
2011-04-14 03:07:05 -04:00
if len(l[0]) > 450:
space = l[0].rfind(" ", 0,430)
2011-02-13 20:32:02 -05:00
if space == -1:
2011-04-14 03:07:05 -04:00
space = 450
elif l[0][space+1:space+5] == "</c>":
space = space+4
2011-04-10 05:22:06 -04:00
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
2011-02-13 20:32:02 -05:00
if len(b) > 0:
return [a] + splittext([b])
else:
return [a]
else:
return l
textl = splittext(textl)
2011-02-15 12:10:57 -05:00
try:
for t in textl:
helpers.msg(self.cli, h, t)
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString, bool)
def startConvo(self, handle, initiated):
h = unicode(handle)
2011-02-15 12:10:57 -05:00
try:
if initiated:
helpers.msg(self.cli, h, "PESTERCHUM:BEGIN")
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def endConvo(self, handle):
h = unicode(handle)
2011-02-15 12:10:57 -05:00
try:
helpers.msg(self.cli, h, "PESTERCHUM:CEASE")
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot()
def updateProfile(self):
me = self.mainwindow.profile()
handle = me.handle
2011-02-15 12:10:57 -05:00
try:
helpers.nick(self.cli, handle)
except socket.error:
self.setConnectionBroken()
self.mainwindow.closeConversations(True)
self.updateMood()
@QtCore.pyqtSlot()
def updateMood(self):
me = self.mainwindow.profile()
2011-02-15 12:10:57 -05:00
try:
helpers.msg(self.cli, "#pesterchum", "MOOD >%d" % (me.mood.value()))
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot()
def updateColor(self):
me = self.mainwindow.profile()
for h in self.mainwindow.convos.keys():
2011-02-15 12:10:57 -05:00
try:
helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd()))
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def blockedChum(self, handle):
h = unicode(handle)
2011-02-15 12:10:57 -05:00
try:
helpers.msg(self.cli, h, "PESTERCHUM:BLOCK")
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def unblockedChum(self, handle):
h = unicode(handle)
2011-02-15 12:10:57 -05:00
try:
helpers.msg(self.cli, h, "PESTERCHUM:UNBLOCK")
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def requestNames(self, channel):
c = unicode(channel)
2011-02-15 12:10:57 -05:00
try:
helpers.names(self.cli, c)
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot()
def requestChannelList(self):
2011-02-15 12:10:57 -05:00
try:
helpers.channel_list(self.cli)
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def joinChannel(self, channel):
c = unicode(channel)
2011-02-15 12:10:57 -05:00
try:
helpers.join(self.cli, c)
2011-05-20 14:45:41 -04:00
helpers.mode(self.cli, c, "", None)
2011-02-15 12:10:57 -05:00
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def leftChannel(self, channel):
c = unicode(channel)
2011-02-15 12:10:57 -05:00
try:
helpers.part(self.cli, c)
except socket.error:
self.setConnectionBroken()
2011-02-06 01:02:39 -05:00
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def kickUser(self, handle, channel):
2011-06-13 16:37:07 -04:00
l = handle.split(":")
2011-02-06 01:02:39 -05:00
c = unicode(channel)
2011-06-13 16:37:07 -04:00
h = unicode(l[0])
if len(l) > 1:
reason = unicode(l[1])
2011-06-28 17:10:26 -04:00
if len(l) > 2:
for x in l[2:]:
reason += unicode(":") + unicode(x)
2011-06-13 16:37:07 -04:00
else:
reason = ""
2011-02-15 12:10:57 -05:00
try:
2011-06-13 16:37:07 -04:00
helpers.kick(self.cli, h, c, reason)
2011-02-15 12:10:57 -05:00
except socket.error:
self.setConnectionBroken()
2011-02-06 19:50:21 -05:00
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString, QtCore.QString)
def setChannelMode(self, channel, mode, command):
c = unicode(channel)
2011-02-15 12:10:57 -05:00
m = unicode(mode)
2011-02-06 19:50:21 -05:00
cmd = unicode(command)
if cmd == "":
cmd = None
2011-02-15 12:10:57 -05:00
try:
helpers.mode(self.cli, c, m, cmd)
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString)
def channelNames(self, channel):
c = unicode(channel)
try:
helpers.names(self.cli, c)
except socket.error:
2011-02-15 12:10:57 -05:00
self.setConnectionBroken()
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def inviteChum(self, handle, channel):
h = unicode(handle)
c = unicode(channel)
try:
helpers.invite(self.cli, h, c)
except socket.error:
self.setConnectionBroken()
@QtCore.pyqtSlot()
def pingServer(self):
try:
self.cli.send("PING %s" % int(time()))
except socket.error:
self.setConnectionBroken()
2011-06-28 19:26:13 -04:00
@QtCore.pyqtSlot(bool)
def setAway(self, away=True):
try:
if away:
self.cli.send("AWAY Idle")
else:
self.cli.send("AWAY")
except socket.error:
self.setConnectionBroken()
2011-06-29 13:19:22 -04:00
@QtCore.pyqtSlot(QtCore.QString, QtCore.QString)
def killSomeQuirks(self, channel, handle):
c = unicode(channel)
h = unicode(handle)
try:
helpers.ctcp(self.cli, c, "NOQUIRKS", h)
except socket.error:
self.setConnectionBroken()
moodUpdated = QtCore.pyqtSignal(QtCore.QString, Mood)
colorUpdated = QtCore.pyqtSignal(QtCore.QString, QtGui.QColor)
messageReceived = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
memoReceived = QtCore.pyqtSignal(QtCore.QString, QtCore.QString, QtCore.QString)
noticeReceived = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
inviteReceived = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
timeCommand = QtCore.pyqtSignal(QtCore.QString, QtCore.QString, QtCore.QString)
namesReceived = QtCore.pyqtSignal(QtCore.QString, PesterList)
channelListReceived = QtCore.pyqtSignal(PesterList)
nickCollision = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
2011-03-06 22:02:47 -05:00
myHandleChanged = QtCore.pyqtSignal(QtCore.QString)
chanInviteOnly = QtCore.pyqtSignal(QtCore.QString)
2011-05-20 14:45:41 -04:00
modesUpdated = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
connected = QtCore.pyqtSignal()
userPresentUpdate = QtCore.pyqtSignal(QtCore.QString, QtCore.QString,
2011-02-15 12:10:57 -05:00
QtCore.QString)
cannotSendToChan = QtCore.pyqtSignal(QtCore.QString, QtCore.QString)
2011-06-20 19:18:47 -04:00
tooManyPeeps = QtCore.pyqtSignal()
2011-06-29 13:19:22 -04:00
quirkDisable = QtCore.pyqtSignal(QtCore.QString, QtCore.QString, QtCore.QString)
2011-02-15 12:10:57 -05:00
class PesterHandler(DefaultCommandHandler):
def notice(self, nick, chan, msg):
try:
msg = msg.decode('utf-8')
except UnicodeDecodeError:
msg = msg.decode('iso-8859-1', 'ignore')
handle = nick[0:nick.find("!")]
logging.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 privmsg(self, nick, chan, msg):
2011-03-03 03:00:38 -05:00
try:
msg = msg.decode('utf-8')
except UnicodeDecodeError:
msg = msg.decode('iso-8859-1', 'ignore')
# display msg, do other stuff
2011-02-07 13:40:05 -05:00
if len(msg) == 0:
return
# silently ignore CTCP
if msg[0] == '\x01':
2011-05-26 03:40:30 -04:00
handle = nick[0:nick.find("!")]
logging.info("---> recv \"CTCP %s :%s\"" % (handle, msg[1:-1]))
if msg[1:-1] == "VERSION":
2011-07-12 03:15:47 -04:00
helpers.ctcp_reply(self.parent.cli, handle, "VERSION", "Pesterchum %s" % (_pcVersion))
2011-06-29 13:19:22 -04:00
elif msg[1:-1].startswith("NOQUIRKS") and chan[0] == "#":
op = nick[0:nick.find("!")]
self.parent.quirkDisable.emit(chan, msg[10:-1], op)
return
handle = nick[0:nick.find("!")]
2011-02-15 12:10:57 -05:00
logging.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",
2011-02-15 12:10:57 -05:00
"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:
colors = [0,0,0]
color = QtGui.QColor(*colors)
self.parent.colorUpdated.emit(handle, color)
else:
self.parent.messageReceived.emit(handle, msg)
2011-02-15 12:10:57 -05:00
def welcome(self, server, nick, msg):
2011-02-18 03:17:13 -05:00
self.parent.setConnected()
2011-02-15 12:10:57 -05:00
helpers.join(self.client, "#pesterchum")
mychumhandle = self.mainwindow.profile().handle
mymood = self.mainwindow.profile().mood.value()
helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood))
def nicknameinuse(self, server, cmd, nick, msg):
newnick = "pesterClient%d" % (random.randint(100,999))
2011-02-15 12:10:57 -05:00
helpers.nick(self.client, newnick)
self.parent.nickCollision.emit(nick, newnick)
2011-02-15 12:10:57 -05:00
def quit(self, nick, reason):
2011-08-10 08:28:31 -04:00
#print reason
handle = nick[0:nick.find("!")]
2011-07-17 04:58:19 -04:00
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")
2011-02-06 01:02:39 -05:00
self.parent.moodUpdated.emit(handle, Mood("offline"))
2011-06-13 16:37:07 -04:00
def kick(self, opnick, channel, handle, reason):
op = opnick[0:opnick.find("!")]
self.parent.userPresentUpdate.emit(handle, channel, "kick:%s:%s" % (op, reason))
2011-02-06 01:02:39 -05:00
# ok i shouldnt be overloading that but am lazy
2011-02-15 12:10:57 -05:00
def part(self, nick, channel, reason="nanchos"):
handle = nick[0:nick.find("!")]
2011-07-10 05:10:36 -04:00
logging.info("---> recv \"PART %s: %s\"" % (handle, channel))
self.parent.userPresentUpdate.emit(handle, channel, "left")
if channel == "#pesterchum":
self.parent.moodUpdated.emit(handle, Mood("offline"))
2011-02-15 12:10:57 -05:00
def join(self, nick, channel):
handle = nick[0:nick.find("!")]
2011-07-10 05:10:36 -04:00
logging.info("---> recv \"JOIN %s: %s\"" % (handle, channel))
self.parent.userPresentUpdate.emit(handle, channel, "join")
if channel == "#pesterchum":
self.parent.moodUpdated.emit(handle, Mood("chummy"))
def mode(self, op, channel, mode, *handles):
if len(handles) <= 0: handles = [""]
opnick = op[0:op.find("!")]
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] == "-":
2011-05-20 14:45:41 -04:00
for i in mode[1:]:
try:
modes.remove(i)
except ValueError:
pass
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))
for (i,m) in enumerate(modes):
try:
self.parent.userPresentUpdate.emit(handles[i], channel, m+":%s" % (op))
except IndexError:
self.parent.userPresentUpdate.emit("", channel, m+":%s" % (op))
2011-02-15 12:10:57 -05:00
def nick(self, oldnick, newnick):
oldhandle = oldnick[0:oldnick.find("!")]
2011-03-06 22:02:47 -05:00
if oldhandle == self.mainwindow.profile().handle:
self.parent.myHandleChanged.emit(newnick)
newchum = PesterProfile(newnick, chumdb=self.mainwindow.chumdb)
2011-02-15 12:10:57 -05:00
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)
2011-02-15 12:10:57 -05:00
def namreply(self, server, nick, op, channel, names):
namelist = names.split(" ")
logging.info("---> recv \"NAMES %s: %d names\"" % (channel, len(namelist)))
if not hasattr(self, 'channelnames'):
self.channelnames = {}
if channel not in self.channelnames:
2011-02-15 12:10:57 -05:00
self.channelnames[channel] = []
self.channelnames[channel].extend(namelist)
def endofnames(self, server, nick, channel, msg):
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"):
self.joined = True
chums = self.mainwindow.chumList.chums
lesschums = []
for c in chums:
chandle = c.handle
if chandle in namelist:
lesschums.append(c)
self.getMood(*lesschums)
2011-02-15 12:10:57 -05:00
def liststart(self, server, handle, *info):
self.channel_list = []
info = list(info)
self.channel_field = info.index("Channel") # dunno if this is protocol
2011-03-31 17:57:30 -04:00
logging.info("---> recv \"CHANNELS: %s " % (self.channel_field))
2011-02-15 12:10:57 -05:00
def list(self, server, handle, *info):
channel = info[self.channel_field]
2011-03-31 17:57:30 -04:00
usercount = info[1]
2011-02-15 12:10:57 -05:00
if channel not in self.channel_list and channel != "#pesterchum":
2011-03-31 17:57:30 -04:00
self.channel_list.append((channel, usercount))
logging.info("---> recv \"CHANNELS: %s " % (channel))
2011-02-15 12:10:57 -05:00
def listend(self, server, handle, msg):
pl = PesterList(self.channel_list)
2011-03-31 17:57:30 -04:00
logging.info("---> recv \"CHANNELS END\"")
2011-02-15 12:10:57 -05:00
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)
2011-05-20 14:45:41 -04:00
def channelmodeis(self, server, handle, channel, modes):
self.parent.modesUpdated.emit(channel, modes)
def cannotsendtochan(self, server, handle, channel, msg):
self.parent.cannotSendToChan.emit(channel, msg)
2011-06-20 19:18:47 -04:00
def toomanypeeps(self, *stuff):
self.parent.tooManyPeeps.emit()
def ping(self, prefix, server):
self.parent.mainwindow.lastping = int(time())
self.client.send('PONG', server)
2011-02-15 12:10:57 -05:00
def getMood(self, *chums):
chumglub = "GETMOOD "
for c in chums:
chandle = c.handle
if len(chumglub+chandle) >= 350:
try:
helpers.msg(self.client, "#pesterchum", chumglub)
except socket.error:
self.parent.setConnectionBroken()
chumglub = "GETMOOD "
chumglub += chandle
if chumglub != "GETMOOD ":
try:
helpers.msg(self.client, "#pesterchum", chumglub)
except socket.error:
self.parent.setConnectionBroken()