diff options
Diffstat (limited to 'services/console/lib/purk/irc.py')
-rw-r--r-- | services/console/lib/purk/irc.py | 328 |
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) |