irc.py

changeset 73
d67cc4fbc3f1
child 76
a2fe9ba3041a
equal deleted inserted replaced
72:2266d6d73de3 73:d67cc4fbc3f1
1 import asyncore
2 import socket
3 import sys
4 import re
5 import modulecore as ModuleCore
6 import traceback
7 from configfile import Config
8 import bt as Bt
9 import hgpoll as HgPoll
10
11 CLIF_CONNECTED = (1 << 1)
12
13 all_clients = []
14
15 class RestartError (Exception):
16 def __init__ (self, value):
17 self.message = value
18 def __str__ (self):
19 return self.message
20
21 #
22 # Prints a line to log channel(s)
23 #
24 def broadcast (line):
25 for client in all_clients:
26 if not client.flags & CLIF_CONNECTED:
27 continue
28
29 for channel in client.channels:
30 if channel.get_value ('logchannel', default=False):
31 client.write ("PRIVMSG %s :%s" % (channel.get_value ('name'), line))
32
33 class logical_exception (Exception):
34 def __init__ (self, value):
35 self.value = value
36 def __str__ (self):
37 return self.value
38
39 #
40 # Main IRC client class
41 #
42 class irc_client (asyncore.dispatcher):
43 def __init__ (self, cfg, flags):
44 global all_clients
45 self.name = cfg.get_value ('name')
46 self.host = cfg.get_value ('address')
47 self.port = cfg.get_value ('port', default=6667)
48 self.password = cfg.get_value ('password', default='')
49 self.channels = cfg.get_nodelist ('channels')
50 self.flags = flags
51 self.send_buffer = []
52 self.umode = cfg.get_value ('umode', default='')
53 self.cfg = cfg
54 self.desired_name = Config.get_value ('nickname', default='cobalt')
55 self.mynick = self.desired_name
56 self.verbose = Config.get_value ('verbose', default=False)
57 self.commandprefix = Config.get_value ('commandprefix', default='.')
58 all_clients.append (self)
59 asyncore.dispatcher.__init__ (self)
60 self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
61 self.connect ((self.host, self.port))
62
63 def register_to_irc (self):
64 ident = Config.get_value ('ident', default='cobalt')
65 gecos = Config.get_value ('gecos', default='cobalt')
66 self.write ("PASS %s" % self.password)
67 self.write ("USER %s * * :%s" % (ident, gecos))
68 self.write ("NICK %s" % self.mynick)
69
70 def handle_connect (self):
71 print "Connected to [%s] %s:%d" % (self.name, self.host, self.port)
72 self.register_to_irc()
73
74 def write (self, utfdata):
75 try:
76 self.send_buffer.append ("%s" % utfdata.decode("utf-8","ignore").encode("ascii","ignore"))
77 except UnicodeEncodeError:
78 pass
79
80 def handle_close (self):
81 print "Connection to [%s] %s:%d terminated." % (self.name, self.host, self.port)
82 self.close()
83
84 def handle_write (self):
85 self.send_all_now()
86
87 def readable (self):
88 return True
89
90 def writable (self):
91 return len (self.send_buffer) > 0
92
93 def send_all_now (self):
94 for line in self.send_buffer:
95 if self.verbose:
96 print "[%s] <- %s" % (self.name, line)
97 self.send ("%s\n" % line)
98 self.send_buffer = []
99
100 def handle_read (self):
101 lines = self.recv (4096).splitlines()
102 for utfline in lines:
103 try:
104 line = utfline.decode("utf-8","ignore").encode("ascii","ignore")
105 except UnicodeDecodeError:
106 continue
107
108 if self.verbose:
109 print "[%s] -> %s" % (self.name, line)
110
111 if line.startswith ("PING :"):
112 self.write ("PONG :%s" % line[6:])
113 else:
114 words = line.split(" ")
115 if len(words) >= 2:
116 if words[1] == "001":
117 self.flags |= CLIF_CONNECTED
118
119 for channel in self.channels:
120 self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default='')))
121
122 umode = self.cfg.get_value ('umode', '')
123
124 if umode != '':
125 self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', '')))
126 elif words[1] == "PRIVMSG":
127 self.handle_privmsg (line)
128 elif words[1] == 'QUIT':
129 rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) QUIT')
130 match = rex.match (line)
131
132 # Try reclaim our nickname if possible
133 if match and match.group(1) == self.desired_name:
134 self.mynick = self.desired_name
135 self.write ("NICK %s" % self.mynick)
136 elif words[1] == "433":
137 #:irc.localhost 433 * cobalt :Nickname is already in use.
138 self.mynick += self.cfg.get_value ('conflictsuffix', default='`')
139 self.write ("NICK " + self.mynick)
140
141 # Check for new issues on the bugtracker
142 Bt.poll()
143
144 # Check for new commits in the repositories
145 HgPoll.poll()
146
147 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
148 #
149 # Handle a PRIVMSG line from the IRC server
150 #
151 def handle_privmsg (self, line):
152 rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) PRIVMSG ([^ ]+) :(.+)$')
153 match = rex.match (line)
154
155 if not match:
156 broadcast ("Recieved bad PRIVMSG: %s" % line)
157
158 sender = match.group (1)
159 user = match.group (2)
160 host = match.group (3)
161 channel = match.group (4)
162 message = match.group (5)
163 replyto = channel if channel != self.mynick else sender
164
165 # Check for command.
166 if len(message) >= 2 and message[0] == self.commandprefix and message[1] != self.commandprefix:
167 stuff = message[1:].split(' ')
168 command = stuff[0]
169 args = stuff[1:]
170 self.handle_command (sender, user, host, replyto, command, args, message)
171 return
172
173 Bt.process_message (self, line, replyto)
174
175 def add_irc_channel (self, channame):
176 for channel in self.channels:
177 if channel.get_value ('name').upper() == channame.upper():
178 return
179
180 channel = self.cfg.append_nodelist ('channels')
181 channel.set_value ('name', channame)
182 self.channels = cfg.get_nodelist ('channels')
183 self.write ('JOIN ' + channame)
184 self.save_config()
185
186 def remove_irc_channel (self, channame):
187 for channel in self.channels:
188 if channel.get_value ('name') == channame:
189 self.channels.remove (channel)
190 break
191 else:
192 return
193
194 self.write ('PART ' + channame)
195 self.save_config()
196
197 def handle_command (self, sender, ident, host, replyto, command, args, message):
198 kvargs = {'sender': sender, 'ident': ident, 'host': host, 'replyto': replyto, 'cmdname': command, 'message': message}
199
200 try:
201 result = ModuleCore.call_command (self, **kvargs)
202
203 if result:
204 return
205 except ModuleCore.CommandError as e:
206 lines = str (e).split ('\n')
207 self.privmsg (replyto, 'error: %s' % lines[0])
208
209 for line in lines[1:]:
210 self.privmsg (replyto, ' ' + line)
211 return
212
213 def handle_error(self):
214 raise RestartError (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))
215
216 def restart(self):
217 raise RestartError()
218
219 def privmsg (self, channel, msg):
220 self.write ("PRIVMSG %s :%s" % (channel, msg))
221
222 def close_connection (self, message):
223 if self.flags & CLIF_CONNECTED:
224 self.write ("QUIT :" + message)
225 self.send_all_now()
226 self.close()
227
228 def quit_irc (self):
229 self.close_connection ('Leaving')
230
231 def exceptdie (self):
232 self.close_connection ('Caught exception')
233
234 def keyboardinterrupt (self):
235 self.close_connection ('KeyboardInterrupt')

mercurial