71 quit() |
96 quit() |
72 |
97 |
73 for aconn in autoconnects: |
98 for aconn in autoconnects: |
74 for conndata in Config.get_nodelist ('connections'): |
99 for conndata in Config.get_nodelist ('connections'): |
75 if conndata.get_value ('name') == aconn: |
100 if conndata.get_value ('name') == aconn: |
76 irc_client (conndata, 0) |
101 Irc.irc_client (conndata, 0) |
77 break |
102 break |
78 else: |
103 else: |
79 raise ValueError ("unknown autoconnect entry %s" % (aconn)) |
104 raise ValueError ("unknown autoconnect entry %s" % (aconn)) |
80 |
105 |
81 g_BotActive = True |
106 g_BotActive = True |
82 asyncore.loop() |
107 asyncore.loop() |
83 except KeyboardInterrupt: |
108 except KeyboardInterrupt: |
84 for client in all_clients: |
109 for client in Irc.all_clients: |
85 client.keyboardinterrupt() |
110 client.keyboardinterrupt() |
86 quit() |
111 quit() |
87 |
112 except Irc.RestartError as e: |
88 # |
113 excepterm (e.message) |
89 # irc_client flags |
|
90 # |
|
91 CLIF_CONNECTED = (1 << 1) |
|
92 |
|
93 # |
|
94 # List of all clients |
|
95 # |
|
96 all_clients = [] |
|
97 |
|
98 class channel (object): |
|
99 name = "" |
|
100 password = "" |
|
101 |
|
102 def __init__ (self, name): |
|
103 self.name = name |
|
104 |
|
105 # |
|
106 # Prints a line to log channel(s) |
|
107 # |
|
108 def chanlog (line): |
|
109 for client in all_clients: |
|
110 if not client.flags & CLIF_CONNECTED: |
|
111 continue |
|
112 |
|
113 for channel in client.channels: |
|
114 if channel.get_value ('logchannel', default=False): |
|
115 client.write ("PRIVMSG %s :%s" % (channel['name'], line)) |
|
116 |
|
117 # |
|
118 # Exception handling |
|
119 # |
|
120 def handle_exception(excType, excValue, trace): |
|
121 excepterm (traceback.format_exception(excType, excValue, trace)) |
|
122 |
|
123 def excepterm(data): |
|
124 for segment in data: |
|
125 for line in segment.splitlines(): |
|
126 print line |
|
127 chanlog (line) |
|
128 |
|
129 for client in all_clients: |
|
130 if len(data) > 0: |
|
131 client.exceptdie() |
|
132 else: |
|
133 client.quit_irc() |
|
134 |
|
135 if g_BotActive: |
|
136 restart_self() |
|
137 else: |
|
138 quit() |
|
139 |
|
140 sys.excepthook = handle_exception |
|
141 |
|
142 def check_admin (sender, ident, host, command): |
|
143 if not "%s@%s" % (ident, host) in g_admins: |
|
144 raise logical_exception (".%s requires admin access" % command) |
|
145 |
|
146 class logical_exception (Exception): |
|
147 def __init__ (self, value): |
|
148 self.value = value |
|
149 def __str__ (self): |
|
150 return self.value |
|
151 |
|
152 # from http://www.daniweb.com/software-development/python/code/260268/restart-your-python-program |
|
153 def restart_self(): |
|
154 python = sys.executable |
|
155 os.execl (python, python, * sys.argv) |
|
156 |
|
157 def plural (a): |
|
158 return '' if a == 1 else 's' |
|
159 |
|
160 # |
|
161 # Main IRC client class |
|
162 # |
|
163 class irc_client (asyncore.dispatcher): |
|
164 def __init__ (self, cfg, flags): |
|
165 self.name = cfg.get_value ('name') |
|
166 self.host = cfg.get_value ('address') |
|
167 self.port = cfg.get_value ('port', default=6667) |
|
168 self.password = cfg.get_value ('password', default='') |
|
169 self.channels = cfg.get_nodelist ('channels') |
|
170 self.flags = flags |
|
171 self.send_buffer = [] |
|
172 self.umode = cfg.get_value ('umode', default='') |
|
173 self.cfg = cfg |
|
174 self.desired_name = Config.get_value ('nickname', default='cobalt') |
|
175 self.mynick = self.desired_name |
|
176 self.verbose = Config.get_value ('verbose', default=False) |
|
177 self.commandprefix = Config.get_value ('commandprefix', default='.') |
|
178 |
|
179 all_clients.append (self) |
|
180 asyncore.dispatcher.__init__ (self) |
|
181 self.create_socket (socket.AF_INET, socket.SOCK_STREAM) |
|
182 self.connect ((self.host, self.port)) |
|
183 |
|
184 def register_to_irc (self): |
|
185 ident = Config.get_value ('ident', default='cobalt') |
|
186 gecos = Config.get_value ('gecos', default='cobalt') |
|
187 self.write ("PASS %s" % self.password) |
|
188 self.write ("USER %s * * :%s" % (ident, gecos)) |
|
189 self.write ("NICK %s" % self.mynick) |
|
190 |
|
191 def handle_connect (self): |
|
192 print "Connected to [%s] %s:%d" % (self.name, self.host, self.port) |
|
193 self.register_to_irc() |
|
194 |
|
195 def write (self, utfdata): |
|
196 try: |
|
197 self.send_buffer.append ("%s" % utfdata.decode("utf-8","ignore").encode("ascii","ignore")) |
|
198 except UnicodeEncodeError: |
|
199 pass |
|
200 |
|
201 def handle_close (self): |
|
202 print "Connection to [%s] %s:%d terminated." % (self.name, self.host, self.port) |
|
203 self.close() |
|
204 |
|
205 def handle_write (self): |
|
206 self.send_all_now() |
|
207 |
|
208 def readable (self): |
|
209 return True |
|
210 |
|
211 def writable (self): |
|
212 return len (self.send_buffer) > 0 |
|
213 |
|
214 def send_all_now (self): |
|
215 for line in self.send_buffer: |
|
216 if self.verbose: |
|
217 print "[%s] <- %s" % (self.name, line) |
|
218 self.send ("%s\n" % line) |
|
219 self.send_buffer = [] |
|
220 |
|
221 def handle_read (self): |
|
222 lines = self.recv (4096).splitlines() |
|
223 for utfline in lines: |
|
224 try: |
|
225 line = utfline.decode("utf-8","ignore").encode("ascii","ignore") |
|
226 except UnicodeDecodeError: |
|
227 continue |
|
228 |
|
229 if self.verbose: |
|
230 print "[%s] -> %s" % (self.name, line) |
|
231 |
|
232 if line.startswith ("PING :"): |
|
233 self.write ("PONG :%s" % line[6:]) |
|
234 else: |
|
235 words = line.split(" ") |
|
236 if len(words) >= 2: |
|
237 if words[1] == "001": |
|
238 self.flags |= CLIF_CONNECTED |
|
239 |
|
240 for channel in self.channels: |
|
241 self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default=''))) |
|
242 |
|
243 umode = self.cfg.get_value ('umode', '') |
|
244 |
|
245 if umode != '': |
|
246 self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', ''))) |
|
247 elif words[1] == "PRIVMSG": |
|
248 self.handle_privmsg (line) |
|
249 elif words[1] == 'QUIT': |
|
250 rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) QUIT') |
|
251 match = rex.match (line) |
|
252 |
|
253 # Try reclaim our nickname if possible |
|
254 if match and match.group(1) == self.desired_name: |
|
255 self.mynick = self.desired_name |
|
256 self.write ("NICK %s" % self.mynick) |
|
257 elif words[1] == "433": |
|
258 #:irc.localhost 433 * cobalt :Nickname is already in use. |
|
259 self.mynick += self.cfg.get_value ('conflictsuffix', default='`') |
|
260 self.write ("NICK " + self.mynick) |
|
261 |
|
262 # Check for new issues on the bugtracker |
|
263 bt_checklatest() |
|
264 |
|
265 # Check for new commits in the repositories |
|
266 HgPoll.poll() |
|
267 |
|
268 def channel_by_name (self, name): |
|
269 for channel in self.channels: |
|
270 if channel.get_value ('name').upper() == args[0].upper(): |
|
271 return channel |
|
272 else: |
|
273 raise logical_exception ('unknown channel ' + args[0]) |
|
274 |
|
275 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # |
|
276 # |
|
277 # Handle a PRIVMSG line from the IRC server |
|
278 # |
|
279 def handle_privmsg (self, line): |
|
280 rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) PRIVMSG ([^ ]+) :(.+)$') |
|
281 match = rex.match (line) |
|
282 if match: |
|
283 sender = match.group (1) |
|
284 user = match.group (2) |
|
285 host = match.group (3) |
|
286 channel = match.group (4) |
|
287 message = match.group (5) |
|
288 replyto = channel if channel != g_mynick else sender |
|
289 |
|
290 # Check for tracker url in the message |
|
291 url = Config.get_node ('bt').get_value ('url') |
|
292 http_regex = re.compile (r'.*http(s?)://%s/view\.php\?id=([0-9]+).*' % url) |
|
293 http_match = http_regex.match (line) |
|
294 |
|
295 # Check for command. |
|
296 if len(message) >= 2 and message[0] == self.commandprefix and message[1] != self.commandprefix: |
|
297 stuff = message[1:].split(' ') |
|
298 command = stuff[0] |
|
299 args = stuff[1:] |
|
300 try: |
|
301 self.handle_command (sender, user, host, replyto, command, args, message) |
|
302 except logical_exception as e: |
|
303 for line in e.value.split ('\n'): |
|
304 if len(line) > 0: |
|
305 self.privmsg (replyto, "error: %s" % line) |
|
306 elif http_match: |
|
307 self.get_ticket_data (replyto, http_match.group (2), False) |
|
308 else: |
|
309 chanlog ("Recieved bad PRIVMSG: %s" % line) |
|
310 |
|
311 def is_admin (self, ident, host): |
|
312 return ("%s@%s" % (ident, host)) in g_admins |
|
313 |
|
314 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # |
|
315 # |
|
316 # Process an IRC command |
|
317 # |
|
318 def handle_command (self, sender, ident, host, replyto, command, args, message): |
|
319 kvargs = {'sender': sender, 'ident': ident, 'host': host, 'replyto': replyto, 'cmdname': command, 'message': message} |
|
320 |
|
321 try: |
|
322 result = ModuleCore.call_command (self, **kvargs) |
|
323 |
|
324 if result: |
|
325 return |
|
326 except ModuleCore.CommandError as e: |
|
327 lines = str (e).split ('\n') |
|
328 self.privmsg (replyto, 'error: %s' % lines[0]) |
|
329 |
|
330 for line in lines[1:]: |
|
331 self.privmsg (replyto, ' ' + line) |
|
332 return |
|
333 #tried |
|
334 |
|
335 if command == 'die': |
|
336 check_admin (sender, ident, host, command) |
|
337 quit() |
|
338 elif command == 'convert': |
|
339 if len(args) != 3 or args[1] != 'as': |
|
340 raise logical_exception ("usage: .%s <value> as <valuetype>" % command) |
|
341 |
|
342 value = float (args[0]) |
|
343 valuetype = args[2] |
|
344 |
|
345 if valuetype in ['radians', 'degrees']: |
|
346 if valuetype == 'radians': |
|
347 radvalue = value |
|
348 degvalue = (value * 180.) / math.pi |
|
349 else: |
|
350 radvalue = (value * math.pi) / 180. |
|
351 degvalue = value |
|
352 |
|
353 self.privmsg (replyto, '%s radians, %s degrees (%s)' %(radvalue, degvalue, degvalue % 360.)) |
|
354 elif valuetype in ['celsius', 'fahrenheit']: |
|
355 if valuetype == 'celsius': |
|
356 celvalue = value |
|
357 fahrvalue = value * 1.8 + 32 |
|
358 else: |
|
359 celvalue = (value - 32) / 1.8 |
|
360 fahrvalue = value |
|
361 |
|
362 self.privmsg (replyto, '%s degrees celsius, %s degrees fahrenheit' %(celvalue, fahrvalue)) |
|
363 else: |
|
364 raise logical_exception ('unknown valuetype, expected one of: degrees, radians (angle conversion), ' + |
|
365 'celsius, fahrenheit (temperature conversion)') |
|
366 elif command == 'urban' or command == 'ud': |
|
367 try: |
|
368 if len(args) < 1: |
|
369 raise logical_exception ('usage: %s <word>' % command) |
|
370 |
|
371 url = 'http://api.urbandictionary.com/v0/define?term=%s' % ('%20'.join (args)) |
|
372 response = urllib2.urlopen (url).read() |
|
373 data = json.loads (response) |
|
374 |
|
375 if 'list' in data and len(data['list']) > 0 and 'word' in data['list'][0] and 'definition' in data['list'][0]: |
|
376 word = data['list'][0]['word'] |
|
377 definition = data['list'][0]['definition'].replace ('\r', ' ').replace ('\n', ' ').replace (' ', ' ') |
|
378 up = data['list'][0]['thumbs_up'] |
|
379 down = data['list'][0]['thumbs_down'] |
|
380 self.privmsg (replyto, "\002%s\002: %s\0033 %d\003 up,\0035 %d\003 down" % (word, definition, up, down)) |
|
381 else: |
|
382 self.privmsg (replyto, "couldn't find a definition of \002%s\002" % args[0]) |
|
383 except logical_exception as e: |
|
384 raise e |
|
385 except Exception as e: |
|
386 raise logical_exception ('Urban dictionary lookup failed: %s' % `e`) |
|
387 # else: |
|
388 # raise logical_exception ("unknown command `.%s`" % command) |
|
389 |
|
390 def handle_error(self): |
|
391 excepterm (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) |
|
392 |
|
393 def restart(self): |
|
394 excepterm('') |
|
395 |
|
396 def privmsg (self, channel, msg): |
|
397 self.write ("PRIVMSG %s :%s" % (channel, msg)) |
|
398 |
|
399 def close_connection (self, message): |
|
400 if self.flags & CLIF_CONNECTED: |
|
401 self.write ("QUIT :" + message) |
|
402 self.send_all_now() |
|
403 self.close() |
|
404 |
|
405 def quit_irc (self): |
|
406 self.close_connection ('Leaving') |
|
407 |
|
408 def exceptdie (self): |
|
409 self.close_connection ('Caught exception') |
|
410 |
|
411 def keyboardinterrupt (self): |
|
412 self.close_connection ('KeyboardInterrupt') |
|
413 |
114 |
414 if __name__ == '__main__': |
115 if __name__ == '__main__': |
415 main() |
116 main() |