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)