15 def parse_irc(msg
, server
):
18 # if our very first character is :
19 # then this is the source,
20 # otherwise insert the server as the source
21 if msg
and msg
[0].startswith(':'):
26 # loop through the msg until we find
27 # something beginning with :
28 for i
, token
in enumerate(msg
):
29 if token
.startswith(':'):
34 msg
[i
:] = [' '.join(msg
[i
:])]
37 # filter out the empty pre-":" tokens and add on the text to the end
38 return [m
for m
in msg
[:-1] if m
] + msg
[-1:]
40 # note: this sucks and makes very little sense, but it matches the BNF
41 # as far as we've tested, which seems to be the goal
45 nicks
= [conf
.get('nick')] + conf
.get('altnicks',[])
48 nicks
= [getpass
.getuser()]
53 class Network(object):
56 def __init__(self
, server
="irc.default.org", port
=6667, nicks
=[],
57 username
="", fullname
="", name
=None, **kwargs
):
61 self
.name
= name
or server
63 self
.nicks
= nicks
or default_nicks()
64 self
.me
= self
.nicks
[0]
66 self
.username
= username
or "urk"
67 self
.fullname
= fullname
or conf
.get("fullname", self
.username
)
73 'CHANMODES': 'b,k,l,imnpstr',
75 self
.prefixes
= {'o':'@', 'h':'%', 'v':'+', '@':'o', '%':'h', '+':'v'}
77 self
.status
= DISCONNECTED
78 self
.failedhosts
= [] #hosts we've tried and failed to connect to
79 self
.channel_prefixes
= '&#+$' # from rfc2812
81 self
.on_channels
= set()
82 self
.requested_joins
= set()
83 self
.requested_parts
= set()
87 #called when we get a result from the dns lookup
88 def on_dns(self
, result
, error
):
90 self
.disconnect(error
=error
[1])
95 #random.shuffle(result)
96 if socket
.has_ipv6
: #prefer ipv6
97 result
= [(f
, t
, p
, c
, a
) for (f
, t
, p
, c
, a
) in result
if f
== socket
.AF_INET6
]+result
98 elif hasattr(socket
,"AF_INET6"): #ignore ipv6
99 result
= [(f
, t
, p
, c
, a
) for (f
, t
, p
, c
, a
) in result
if f
!= socket
.AF_INET6
]
101 self
.failedlasthost
= False
103 for f
, t
, p
, c
, a
in result
:
104 if (f
, t
, p
, c
, a
) not in self
.failedhosts
:
106 self
.socket
= socket
.socket(f
, t
, p
)
109 self
.source
= ui
.fork(self
.on_connect
, self
.socket
.connect
, a
)
110 self
.failedhosts
.append((f
, t
, p
, c
, a
))
111 if set(self
.failedhosts
) >= set(result
):
112 self
.failedlasthost
= True
115 self
.failedlasthost
= True
117 self
.failedhosts
[:] = (f
, t
, p
, c
, a
),
118 f
, t
, p
, c
, a
= result
[0]
120 self
.socket
= socket
.socket(f
, t
, p
)
121 self
.source
= ui
.fork(self
.on_connect
, self
.socket
.connect
, a
)
123 self
.disconnect(error
="Couldn't find a host we can connect to")
125 self
.disconnect(error
="Couldn't find a host we can connect to")
127 #called when socket.open() returns
128 def on_connect(self
, result
, error
):
130 self
.disconnect(error
=error
[1])
131 #we should immediately retry if we failed to open the socket and there are hosts left
132 if self
.status
== DISCONNECTED
and not self
.failedlasthost
:
133 windows
.get_default(self
).write("* Retrying with next available host")
136 self
.source
= source
= ui
.Source()
137 self
.status
= INITIALIZING
138 self
.failedhosts
[:] = ()
140 events
.trigger('SocketConnect', network
=self
)
143 self
.source
= ui
.fork(self
.on_read
, self
.socket
.recv
, 8192)
145 #called when we read data or failed to read data
146 def on_read(self
, result
, error
):
148 self
.disconnect(error
=error
[1])
150 self
.disconnect(error
="Connection closed by remote host")
152 self
.source
= source
= ui
.Source()
154 self
.buffer = (self
.buffer + result
).split("\r\n")
156 for line
in self
.buffer[:-1]:
160 self
.buffer = self
.buffer[-1]
165 self
.source
= ui
.fork(self
.on_read
, self
.socket
.recv
, 8192)
168 events
.trigger("OwnRaw", network
=self
, raw
=msg
)
170 if self
.status
>= INITIALIZING
:
171 self
.socket
.send(msg
+ "\r\n")
173 def got_msg(self
, msg
):
174 pmsg
= parse_irc(msg
, self
.server
)
176 e_data
= events
.data(
181 window
=windows
.get_default(self
)
185 e_data
.source
, e_data
.address
= pmsg
[0].split('!',1)
188 e_data
.source
, e_data
.address
= pmsg
[0], ''
191 e_data
.target
= pmsg
[2]
193 e_data
.target
= pmsg
[-1]
195 events
.trigger('Raw', e_data
)
199 self
.status
= CONNECTING
201 self
.source
= ui
.fork(self
.on_dns
, socket
.getaddrinfo
, self
.server
, self
.port
, 0, socket
.SOCK_STREAM
)
203 events
.trigger('Connecting', network
=self
)
205 def disconnect(self
, error
=None):
210 self
.source
.unregister()
215 self
.status
= DISCONNECTED
217 #note: connecting from onDisconnect is probably a Bad Thing
218 events
.trigger('Disconnect', network
=self
, error
=error
)
220 #trigger a nick change if the nick we want is different from the one we
222 if self
.me
!= self
.nicks
[0]:
224 'Nick', network
=self
, window
=windows
.get_default(self
),
225 source
=self
.me
, target
=self
.nicks
[0], address
='',
228 self
.me
= self
.nicks
[0]
230 def norm_case(self
, string
):
231 return string
.lower()
233 def quit(self
, msg
=None):
237 msg
= conf
.get('quitmsg', "%s - %s" % (urk
.long_version
, urk
.website
))
238 self
.raw("QUIT :%s" % msg
)
243 def join(self
, target
, key
='', requested
=True):
246 self
.raw("JOIN %s%s" % (target
,key
))
248 for chan
in target
.split(' ',1)[0].split(','):
250 self
.requested_parts
.update(self
.on_channels
)
252 self
.requested_joins
.add(self
.norm_case(chan
))
254 def part(self
, target
, msg
="", requested
=True):
258 self
.raw("PART %s%s" % (target
, msg
))
260 for chan
in target
.split(' ',1)[0].split(','):
261 self
.requested_parts
.add(self
.norm_case(target
))
263 def msg(self
, target
, msg
):
264 self
.raw("PRIVMSG %s :%s" % (target
, msg
))
267 'OwnText', source
=self
.me
, target
=str(target
), text
=msg
,
268 network
=self
, window
=windows
.get_default(self
)
271 def notice(self
, target
, msg
):
272 self
.raw("NOTICE %s :%s" % (target
, msg
))
275 'OwnNotice', source
=self
.me
, target
=str(target
), text
=msg
,
276 network
=self
, window
=windows
.get_default(self
)
279 #a Network that is never connected
280 class DummyNetwork(Network
):
281 def __nonzero__(self
):
285 Network
.__init
__(self
)
287 self
.name
= self
.server
= self
.isupport
['NETWORK'] = "None"
290 raise NotImplementedError, "Cannot connect dummy network."
293 raise NotImplementedError, "Cannot send %s over the dummy network." % repr(msg
)
295 dummy_network
= DummyNetwork()
297 #this was ported from srvx's tools.c
298 def match_glob(text
, glob
, t
=0, g
=0):
316 for i
in xrange(t
, len(text
)):
317 if text
[i
] == glob
[g
] and match_glob(text
, glob
, i
+1, g
+1):
321 if t
== len(text
) and g
== len(glob
):
323 if t
== len(text
) or g
== len(glob
) or text
[t
] != glob
[g
]:
327 return t
== len(text
)