|
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') |