Added a maximum ping count condition to the IRC client
[nil.git] / nil / irc.py
blobc8b6eaae2ac63fa610a1fc79450c0ffa8d66ef98
1 from __future__ import absolute_import
2 import socket, random, time, nil.thread
4 colon = ':'
5 delimiter = ' '
7 def do_nothing(*arguments):
8 pass
10 def strip_tags(input):
11 output = ''
13 i = 0
14 last_index = len(input) - 1
15 while i <= last_index:
16 current_char = input[i]
17 if ord(current_char) < ord(' '):
18 if current_char == '\x03' and last_index - i >= 2:
19 next_char = input[i + 1]
20 next_char_value = ord(next_char)
21 if next_char == '1':
22 i += 1
23 second_digit = ord(input[i + 1])
24 if second_digit >= ord('0') and second_digit <= ord('5'):
25 i += 1
26 elif next_char == '0':
27 i += 2
28 elif next_char_value >= ord('2') and next_char_value <= ord('9'):
29 i += 1
30 else:
31 output += current_char
32 i += 1
33 return output
35 class irc_user:
36 def __init__(self, input):
37 self.raw = input
38 self.error = False
40 tokens = input[1 : ].split('!')
41 if len(tokens) != 2:
42 self.error = True
43 return
45 self.nick = tokens[0]
47 tokens = tokens[1].split('@')
48 if len(tokens) != 2:
49 self.error = True
50 return
52 self.ident = tokens[0]
53 self.address = tokens[1]
55 class irc_client:
56 def __init__(self):
57 self.on_raw_packet = do_nothing
58 self.on_failed_connect = do_nothing
59 self.on_connect = do_nothing
60 self.on_disconnect = do_nothing
61 self.on_entry = do_nothing
62 self.on_notice = do_nothing
63 self.on_join = do_nothing
64 self.on_invite = do_nothing
65 self.on_channel_message = do_nothing
66 self.on_private_message = do_nothing
67 self.on_nick_name_in_use = self.change_nick
68 self.on_quit = do_nothing
70 self.auto_reconnect = True
71 self.auto_reconnect_delay = 5
72 self.read_size = 1024
73 self.last_line = None
74 self.timeout = 600
76 self.ping_counter = 0
77 self.maximum_ping_count = None
79 nil.thread.create_thread(lambda: self.timeout_thread())
81 def timeout_thread(self):
82 while True:
83 if self.last_line != None and time.time() - self.last_line > self.timeout:
84 print 'Timeout occured'
85 nil.thread.create_thread(lambda: self.perform_reconnect())
86 time.sleep(self.auto_reconnect_delay)
87 continue
88 time.sleep(1)
90 def connect(self, server, port, nick, user, local_host, real_name):
91 self.server = server
92 self.port = port
93 self.nick = nick
94 self.user = user
95 self.local_host = local_host
96 self.real_name = real_name
98 while True:
99 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
100 try:
101 self.socket.connect((server, port))
102 self.on_connect()
103 break
104 except:
105 self.on_failed_connect()
106 if self.auto_reconnect:
107 time.sleep(self.auto_reconnect_delay)
108 else:
109 return
111 self.send_line('NICK %s' % nick)
112 self.send_line('USER %s "%s" "%s" :%s' % (user, local_host, server, real_name))
113 self.receive_data()
115 def reconnect(self):
116 self.connect(self.server, self.port, self.nick, self.user, self.local_host, self.real_name)
118 def send_line(self, line):
119 self.socket.send('%s\r\n' % line)
121 def join(self, channel):
122 self.send_line('JOIN %s' % channel)
124 def message(self, target, message):
125 self.send_line('PRIVMSG %s :%s' % (target, message))
127 def got_disconnected(self):
128 self.on_disconnect()
129 time.sleep(self.auto_reconnect_delay)
130 self.reconnect()
132 def perform_reconnect(self):
133 try:
134 self.socket.close()
135 except IOError:
136 pass
137 self.got_disconnected()
139 def receive_data(self):
140 self.buffer = ''
141 while True:
142 try:
143 new_data = self.socket.recv(self.read_size)
144 self.last_line = time.time()
145 except IOError:
146 self.perform_reconnect()
147 return
148 if len(new_data) == 0:
149 self.perform_reconnect()
150 return
151 self.buffer += new_data
152 self.process_new_data()
154 def process_new_data(self):
155 while True:
156 offset = self.buffer.find('\n')
157 if offset == -1:
158 break
159 line = self.buffer[0 : offset].replace('\r', '')
160 self.buffer = self.buffer[offset + 1 : ]
161 self.process_line(line)
163 def process_line(self, line):
164 self.on_raw_packet(line)
166 if len(line) == 0:
167 return
168 offset = line.find(colon, 1)
169 if offset == -1:
170 tokens = line.split(delimiter)
171 else:
172 tokens = line[0 : offset - 1].split(delimiter)
173 tokens.append(line[offset + 1 : ])
175 self.process_command(tokens)
177 def process_command(self, tokens):
178 commands = [
179 ('376', self.event_end_of_motd),
180 ('422', self.event_end_of_motd),
181 ('433', self.event_nick_name_in_use),
182 ('NOTICE', self.event_notice),
183 ('INVITE', self.event_invite),
184 ('JOIN', self.event_join),
185 ('PRIVMSG', self.event_message),
186 ('MODE', self.event_mode),
187 ('QUIT', self.event_quit)
190 if tokens[0] == 'PING' and len(tokens) == 2:
191 self.send_line('PONG %s' % tokens[1])
192 self.ping_counter += 1
193 if self.maximum_ping_count != None and self.ping_counter >= self.maximum_ping_count:
194 print 'Exceeded maximum ping count, reconnecting'
195 self.perform_reconnect()
196 return
198 if len(tokens) < 3:
199 return
201 self.ping_counter = 0
203 for command, function in commands:
204 if command == tokens[1]:
205 function(tokens)
206 break
208 def change_nick(self):
209 new_nick = '%s%d' % (self.nick, random.randint(10, 99))
210 self.send_line('NICK %s' % new_nick)
212 def event_end_of_motd(self, tokens):
213 self.nick = tokens[2]
214 self.on_entry()
216 def event_nick_name_in_use(self, tokens):
217 self.on_nick_name_in_use()
219 def event_notice(self, tokens):
220 user = irc_user(tokens[0])
221 if user.error:
222 return
223 text = tokens[-1]
224 self.on_notice(user, text)
226 def event_invite(self, tokens):
227 user = irc_user(tokens[0])
228 if user.error:
229 return
230 channel = tokens[-1]
231 self.on_invite(user, channel)
233 def event_join(self, tokens):
234 user = irc_user(tokens[0])
235 if user.error:
236 return
237 own_join = (user.nick == self.nick)
238 channel = tokens[-1]
239 self.on_join(channel, user, own_join)
241 def event_message(self, tokens):
242 user = irc_user(tokens[0])
243 if user.error:
244 return
245 target = tokens[2]
246 message = tokens[-1]
247 if target == self.nick:
248 self.on_private_message(user, message)
249 else:
250 self.on_channel_message(target, user, message)
252 def event_mode(self, tokens):
253 pass
255 def event_quit(self, tokens):
256 user = irc_user(tokens[0])
257 if user.error:
258 return
259 message = tokens[-1]
260 self.on_quit(user, message)