Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/services/console/lib/purk/irc.py
diff options
context:
space:
mode:
Diffstat (limited to 'services/console/lib/purk/irc.py')
-rw-r--r--services/console/lib/purk/irc.py328
1 files changed, 328 insertions, 0 deletions
diff --git a/services/console/lib/purk/irc.py b/services/console/lib/purk/irc.py
new file mode 100644
index 0000000..d5a01aa
--- /dev/null
+++ b/services/console/lib/purk/irc.py
@@ -0,0 +1,328 @@
+import socket
+import sys
+
+from conf import conf
+import ui
+import windows
+import info
+
+DISCONNECTED = 0
+CONNECTING = 1
+INITIALIZING = 2
+CONNECTED = 3
+
+def parse_irc(msg, server):
+ msg = msg.split(' ')
+
+ # if our very first character is :
+ # then this is the source,
+ # otherwise insert the server as the source
+ if msg and msg[0].startswith(':'):
+ msg[0] = msg[0][1:]
+ else:
+ msg.insert(0, server)
+
+ # loop through the msg until we find
+ # something beginning with :
+ for i, token in enumerate(msg):
+ if token.startswith(':'):
+ # remove the :
+ msg[i] = msg[i][1:]
+
+ # join up the rest
+ msg[i:] = [' '.join(msg[i:])]
+ break
+
+ # filter out the empty pre-":" tokens and add on the text to the end
+ return [m for m in msg[:-1] if m] + msg[-1:]
+
+ # note: this sucks and makes very little sense, but it matches the BNF
+ # as far as we've tested, which seems to be the goal
+
+def default_nicks():
+ try:
+ nicks = [conf.get('nick')] + conf.get('altnicks',[])
+ if not nicks[0]:
+ import getpass
+ nicks = [getpass.getuser()]
+ except:
+ nicks = ["mrurk"]
+ return nicks
+
+class Network(object):
+ socket = None
+
+ def __init__(self, core, server="irc.default.org", port=6667, nicks=[],
+ username="", fullname="", name=None, **kwargs):
+ self.manager = core.manager
+ self.server = server
+ self.port = port
+ self.events = core.events
+
+ self.name = name or server
+
+ self.nicks = nicks or default_nicks()
+ self.me = self.nicks[0]
+
+ self.username = username or "urk"
+ self.fullname = fullname or conf.get("fullname", self.username)
+ self.password = ''
+
+ self.isupport = {
+ 'NETWORK': server,
+ 'PREFIX': '(ohv)@%+',
+ 'CHANMODES': 'b,k,l,imnpstr',
+ }
+ self.prefixes = {'o':'@', 'h':'%', 'v':'+', '@':'o', '%':'h', '+':'v'}
+
+ self.status = DISCONNECTED
+ self.failedhosts = [] #hosts we've tried and failed to connect to
+ self.channel_prefixes = '&#+$' # from rfc2812
+
+ self.on_channels = set()
+ self.requested_joins = set()
+ self.requested_parts = set()
+
+ self.buffer = ''
+
+ #called when we get a result from the dns lookup
+ def on_dns(self, result, error):
+ if error:
+ self.disconnect(error=error[1])
+ else:
+ #import os
+ #import random
+ #random.seed()
+ #random.shuffle(result)
+ if socket.has_ipv6: #prefer ipv6
+ result = [(f, t, p, c, a) for (f, t, p, c, a) in result if f == socket.AF_INET6]+result
+ elif hasattr(socket,"AF_INET6"): #ignore ipv6
+ result = [(f, t, p, c, a) for (f, t, p, c, a) in result if f != socket.AF_INET6]
+
+ self.failedlasthost = False
+
+ for f, t, p, c, a in result:
+ if (f, t, p, c, a) not in self.failedhosts:
+ try:
+ self.socket = socket.socket(f, t, p)
+ except:
+ continue
+ self.source = ui.fork(self.on_connect, self.socket.connect, a)
+ self.failedhosts.append((f, t, p, c, a))
+ if set(self.failedhosts) >= set(result):
+ self.failedlasthost = True
+ break
+ else:
+ self.failedlasthost = True
+ if len(result):
+ self.failedhosts[:] = (f, t, p, c, a),
+ f, t, p, c, a = result[0]
+ try:
+ self.socket = socket.socket(f, t, p)
+ self.source = ui.fork(self.on_connect, self.socket.connect, a)
+ except:
+ self.disconnect(error="Couldn't find a host we can connect to")
+ else:
+ self.disconnect(error="Couldn't find a host we can connect to")
+
+ #called when socket.open() returns
+ def on_connect(self, result, error):
+ if error:
+ self.disconnect(error=error[1])
+ #we should immediately retry if we failed to open the socket and there are hosts left
+ if self.status == DISCONNECTED and not self.failedlasthost:
+ windows.get_default(self).write("* Retrying with next available host")
+ self.connect()
+ else:
+ self.source = source = ui.Source()
+ self.status = INITIALIZING
+ self.failedhosts[:] = ()
+
+ self.events.trigger('SocketConnect', network=self)
+
+ if source.enabled:
+ self.source = ui.fork(self.on_read, self.socket.recv, 8192)
+
+ #called when we read data or failed to read data
+ def on_read(self, result, error):
+ if error:
+ self.disconnect(error=error[1])
+ elif not result:
+ self.disconnect(error="Connection closed by remote host")
+ else:
+ self.source = source = ui.Source()
+
+ self.buffer = (self.buffer + result).split("\r\n")
+
+ for line in self.buffer[:-1]:
+ self.got_msg(line)
+
+ if self.buffer:
+ self.buffer = self.buffer[-1]
+ else:
+ self.buffer = ''
+
+ if source.enabled:
+ self.source = ui.fork(self.on_read, self.socket.recv, 8192)
+
+ def raw(self, msg):
+ self.events.trigger("OwnRaw", network=self, raw=msg)
+
+ if self.status >= INITIALIZING:
+ self.socket.send(msg + "\r\n")
+
+ def got_msg(self, msg):
+ pmsg = parse_irc(msg, self.server)
+
+ e_data = self.events.data(
+ raw=msg,
+ msg=pmsg,
+ text=pmsg[-1],
+ network=self,
+ window=windows.get_default(self, self.manager)
+ )
+
+ if "!" in pmsg[0]:
+ e_data.source, e_data.address = pmsg[0].split('!',1)
+
+ else:
+ e_data.source, e_data.address = pmsg[0], ''
+
+ if len(pmsg) > 2:
+ e_data.target = pmsg[2]
+ else:
+ e_data.target = pmsg[-1]
+
+ self.events.trigger('Raw', e_data)
+
+ def connect(self):
+ if not self.status:
+ self.status = CONNECTING
+
+ self.source = ui.fork(self.on_dns, socket.getaddrinfo, self.server, self.port, 0, socket.SOCK_STREAM)
+
+ self.events.trigger('Connecting', network=self)
+
+ def disconnect(self, error=None):
+ if self.socket:
+ self.socket.close()
+
+ if self.source:
+ self.source.unregister()
+ self.source = None
+
+ self.socket = None
+
+ self.status = DISCONNECTED
+
+ #note: connecting from onDisconnect is probably a Bad Thing
+ self.events.trigger('Disconnect', network=self, error=error)
+
+ #trigger a nick change if the nick we want is different from the one we
+ # had.
+ if self.me != self.nicks[0]:
+ self.events.trigger(
+ 'Nick', network=self, window=windows.get_default(self),
+ source=self.me, target=self.nicks[0], address='',
+ text=self.nicks[0]
+ )
+ self.me = self.nicks[0]
+
+ def norm_case(self, string):
+ return string.lower()
+
+ def quit(self, msg=None):
+ if self.status:
+ try:
+ if msg == None:
+ msg = conf.get('quitmsg', "%s - %s" % (info.long_version, info.website))
+ self.raw("QUIT :%s" % msg)
+ except:
+ pass
+ self.disconnect()
+
+ def join(self, target, key='', requested=True):
+ if key:
+ key = ' '+key
+ self.raw("JOIN %s%s" % (target,key))
+ if requested:
+ for chan in target.split(' ',1)[0].split(','):
+ if chan == '0':
+ self.requested_parts.update(self.on_channels)
+ else:
+ self.requested_joins.add(self.norm_case(chan))
+
+ def part(self, target, msg="", requested=True):
+ if msg:
+ msg = " :" + msg
+
+ self.raw("PART %s%s" % (target, msg))
+ if requested:
+ for chan in target.split(' ',1)[0].split(','):
+ self.requested_parts.add(self.norm_case(target))
+
+ def msg(self, target, msg):
+ self.raw("PRIVMSG %s :%s" % (target, msg))
+
+ self.events.trigger(
+ 'OwnText', source=self.me, target=str(target), text=msg,
+ network=self, window=windows.get_default(self, self.manager)
+ )
+
+ def notice(self, target, msg):
+ self.raw("NOTICE %s :%s" % (target, msg))
+
+ self.events.trigger(
+ 'OwnNotice', source=self.me, target=str(target), text=msg,
+ network=self, window=windows.get_default(self)
+ )
+
+#a Network that is never connected
+class DummyNetwork(Network):
+ def __nonzero__(self):
+ return False
+
+ def __init__(self, core):
+ Network.__init__(self, core)
+
+ self.name = self.server = self.isupport['NETWORK'] = "None"
+
+ def connect(self):
+ raise NotImplementedError, "Cannot connect dummy network."
+
+ def raw(self, msg):
+ raise NotImplementedError, "Cannot send %s over the dummy network." % repr(msg)
+
+#dummy_network = DummyNetwork()
+
+#this was ported from srvx's tools.c
+def match_glob(text, glob, t=0, g=0):
+ while g < len(glob):
+ if glob[g] in '?*':
+ star_p = q_cnt = 0
+ while g < len(glob):
+ if glob[g] == '*':
+ star_p = True
+ elif glob[g] == '?':
+ q_cnt += 1
+ else:
+ break
+ g += 1
+ t += q_cnt
+ if t > len(text):
+ return False
+ if star_p:
+ if g == len(glob):
+ return True
+ for i in xrange(t, len(text)):
+ if text[i] == glob[g] and match_glob(text, glob, i+1, g+1):
+ return True
+ return False
+ else:
+ if t == len(text) and g == len(glob):
+ return True
+ if t == len(text) or g == len(glob) or text[t] != glob[g]:
+ return False
+ t += 1
+ g += 1
+ return t == len(text)