|
1 #! /usr/bin/env python |
|
2 |
|
3 import sys |
|
4 import os |
|
5 os.chdir(os.path.dirname(sys.argv[0])) |
|
6 |
|
7 sys.path.insert(1, 'modules') |
|
8 |
|
9 import xmpp |
|
10 import string |
|
11 import time |
|
12 import thread |
|
13 import random |
|
14 import types |
|
15 import traceback |
|
16 import getopt |
|
17 |
|
18 ################################################################################ |
|
19 |
|
20 CONFIGURATION_FILE = 'dynamic/config.cfg' |
|
21 |
|
22 GENERAL_CONFIG_FILE = 'config.txt' |
|
23 |
|
24 fp = open(GENERAL_CONFIG_FILE, 'r') |
|
25 GENERAL_CONFIG = eval(fp.read()) |
|
26 fp.close() |
|
27 |
|
28 SERVER = GENERAL_CONFIG['SERVER'] |
|
29 PORT = GENERAL_CONFIG['PORT'] |
|
30 USERNAME = GENERAL_CONFIG['USERNAME'] |
|
31 PASSWORD = GENERAL_CONFIG['PASSWORD'] |
|
32 RESOURCE = GENERAL_CONFIG['RESOURCE'] |
|
33 |
|
34 NICKS_CACHE_FILE = 'dynamic/chatnicks.cfg' |
|
35 GROUPCHAT_CACHE_FILE = 'dynamic/chatrooms.cfg' |
|
36 ACCESS_FILE = 'dynamic/access.cfg' |
|
37 PLUGIN_DIR = 'plugins' |
|
38 |
|
39 DEFAULT_NICK = GENERAL_CONFIG['DEFAULT_NICK'] |
|
40 ADMINS = GENERAL_CONFIG['ADMINS'] |
|
41 ADMIN_PASSWORD = GENERAL_CONFIG['ADMIN_PASSWORD'] |
|
42 |
|
43 AUTO_RESTART = GENERAL_CONFIG['AUTO_RESTART'] |
|
44 |
|
45 PUBLIC_LOG_DIR = GENERAL_CONFIG['PUBLIC_LOG_DIR'] |
|
46 PRIVATE_LOG_DIR = GENERAL_CONFIG['PRIVATE_LOG_DIR'] |
|
47 |
|
48 INITSCRIPT_FILE = GENERAL_CONFIG['INITSCRIPT_FILE'] |
|
49 |
|
50 |
|
51 ################################################################################ |
|
52 |
|
53 COMMANDS = {} |
|
54 |
|
55 GROUPCHATS = {} |
|
56 |
|
57 MESSAGE_HANDLERS = [] |
|
58 OUTGOING_MESSAGE_HANDLERS = [] |
|
59 JOIN_HANDLERS = [] |
|
60 LEAVE_HANDLERS = [] |
|
61 IQ_HANDLERS = [] |
|
62 PRESENCE_HANDLERS = [] |
|
63 GROUPCHAT_INVITE_HANDLERS = [] |
|
64 |
|
65 COMMAND_HANDLERS = {} |
|
66 |
|
67 ACCESS = {} |
|
68 |
|
69 JCON = None |
|
70 |
|
71 CONFIGURATION = {} |
|
72 |
|
73 ################################################################################ |
|
74 |
|
75 optlist, args = getopt.getopt(sys.argv[1:], '', ['pid=']) |
|
76 for opt_tuple in optlist: |
|
77 if opt_tuple[0] == '--pid': |
|
78 pid_filename = opt_tuple[1] |
|
79 fp = open(pid_filename, 'w') |
|
80 fp.write(str(os.getpid())) |
|
81 fp.close() |
|
82 |
|
83 ################################################################################ |
|
84 |
|
85 def initialize_file(filename, data=''): |
|
86 if not os.access(filename, os.F_OK): |
|
87 fp = file(filename, 'w') |
|
88 if data: |
|
89 fp.write(data) |
|
90 fp.close() |
|
91 |
|
92 def read_file(filename): |
|
93 fp = file(filename) |
|
94 data = fp.read() |
|
95 fp.close() |
|
96 return data |
|
97 |
|
98 def write_file(filename, data): |
|
99 fp = file(filename, 'w') |
|
100 fp.write(data) |
|
101 fp.close() |
|
102 |
|
103 ################################################################################ |
|
104 |
|
105 initialize_file(CONFIGURATION_FILE, '{}') |
|
106 try: |
|
107 CONFIGURATION = eval(read_file(CONFIGURATION_FILE)) |
|
108 except: |
|
109 print 'Error Parsing Configuration File' |
|
110 |
|
111 def config_get(category, key): |
|
112 if CONFIGURATION.has_key(category): |
|
113 if CONFIGURATION[category].has_key(key): |
|
114 return CONFIGURATION[category][key] |
|
115 else: |
|
116 return None |
|
117 else: |
|
118 return None |
|
119 |
|
120 def config_set(category, key, value): |
|
121 if not CONFIGURATION.has_key(category): |
|
122 CONFIGURATION[category] = {} |
|
123 CONFIGURATION[category][key] = value |
|
124 config_string = '{\n' |
|
125 for category in CONFIGURATION.keys(): |
|
126 config_string += repr(category) + ':\n' |
|
127 for key in CONFIGURATION[category].keys(): |
|
128 config_string += '\t' + repr(key) + ': ' + repr(CONFIGURATION[category][key]) + '\n' |
|
129 config_string += '\n' |
|
130 config_string += '}' |
|
131 write_file(CONFIGURATION_FILE, config_string) |
|
132 |
|
133 ################################################################################ |
|
134 |
|
135 def register_message_handler(instance): |
|
136 MESSAGE_HANDLERS.append(instance) |
|
137 def register_outgoing_message_handler(instance): |
|
138 OUTGOING_MESSAGE_HANDLERS.append(instance) |
|
139 def register_join_handler(instance): |
|
140 JOIN_HANDLERS.append(instance) |
|
141 def register_leave_handler(instance): |
|
142 LEAVE_HANDLERS.append(instance) |
|
143 def register_iq_handler(instance): |
|
144 IQ_HANDLERS.append(instance) |
|
145 def register_presence_handler(instance): |
|
146 PRESENCE_HANDLERS.append(instance) |
|
147 def register_groupchat_invite_handler(instance): |
|
148 GROUPCHAT_INVITE_HANDLERS.append(instance) |
|
149 |
|
150 def register_command_handler(instance, command, access=0, description='', syntax='', examples=[]): |
|
151 COMMAND_HANDLERS[command] = instance |
|
152 COMMANDS[command] = {'access': access, 'description': description, 'syntax': syntax, 'examples': examples} |
|
153 |
|
154 def call_message_handlers(type, source, body): |
|
155 for handler in MESSAGE_HANDLERS: |
|
156 thread.start_new(handler, (type, source, body)) |
|
157 def call_outgoing_message_handlers(target, body): |
|
158 for handler in OUTGOING_MESSAGE_HANDLERS: |
|
159 thread.start_new(handler, (target, body)) |
|
160 def call_join_handlers(groupchat, nick): |
|
161 for handler in JOIN_HANDLERS: |
|
162 thread.start_new(handler, (groupchat, nick)) |
|
163 def call_leave_handlers(groupchat, nick): |
|
164 for handler in LEAVE_HANDLERS: |
|
165 thread.start_new(handler, (groupchat, nick)) |
|
166 def call_iq_handlers(iq): |
|
167 for handler in IQ_HANDLERS: |
|
168 thread.start_new(handler, (iq,)) |
|
169 def call_presence_handlers(prs): |
|
170 for handler in PRESENCE_HANDLERS: |
|
171 thread.start_new(handler, (prs,)) |
|
172 def call_groupchat_invite_handlers(source, groupchat, body): |
|
173 for handler in GROUPCHAT_INVITE_HANDLERS: |
|
174 thread.start_new(handler, (source, groupchat, body)) |
|
175 |
|
176 def call_command_handlers(command, type, source, parameters): |
|
177 if COMMAND_HANDLERS.has_key(command): |
|
178 if has_access(source, COMMANDS[command]['access']): |
|
179 thread.start_new(COMMAND_HANDLERS[command], (type, source, parameters)) |
|
180 else: |
|
181 smsg(type, source, 'Unauthorized') |
|
182 |
|
183 ################################################################################ |
|
184 |
|
185 def find_plugins(): |
|
186 valid_plugins = [] |
|
187 possibilities = os.listdir('plugins') |
|
188 for possibility in possibilities: |
|
189 if possibility[-3:].lower() == '.py': |
|
190 try: |
|
191 fp = file(PLUGIN_DIR + '/' + possibility) |
|
192 data = fp.read(20) |
|
193 if data == '#$ neutron_plugin 01': |
|
194 valid_plugins.append(possibility) |
|
195 except: |
|
196 pass |
|
197 return valid_plugins |
|
198 |
|
199 def load_plugins(): |
|
200 valid_plugins = find_plugins() |
|
201 for valid_plugin in valid_plugins: |
|
202 try: |
|
203 print 'Plugin: ' + valid_plugin |
|
204 #execfile(PLUGIN_DIR + '/' + valid_plugin) |
|
205 fp = file(PLUGIN_DIR + '/' + valid_plugin) |
|
206 exec fp in globals() |
|
207 fp.close() |
|
208 except: |
|
209 raise |
|
210 |
|
211 def load_initscript(): |
|
212 print 'Executing Init Script' |
|
213 fp = file(INITSCRIPT_FILE) |
|
214 exec fp in globals() |
|
215 fp.close() |
|
216 |
|
217 |
|
218 ################################################################################ |
|
219 |
|
220 def get_true_jid(jid): |
|
221 true_jid = '' |
|
222 if type(jid) is types.ListType: |
|
223 jid = jid[0] |
|
224 if type(jid) is types.InstanceType: |
|
225 jid = unicode(jid) # str(jid) |
|
226 stripped_jid = string.split(jid, '/', 1)[0] |
|
227 resource = '' |
|
228 if len(string.split(jid, '/', 1)) == 2: |
|
229 resource = string.split(jid, '/', 1)[1] |
|
230 if GROUPCHATS.has_key(stripped_jid): |
|
231 if GROUPCHATS[stripped_jid].has_key(resource): |
|
232 true_jid = string.split(unicode(GROUPCHATS[stripped_jid][resource]['jid']), '/', 1)[0] |
|
233 else: |
|
234 true_jid = stripped_jid |
|
235 else: |
|
236 true_jid = stripped_jid |
|
237 return true_jid |
|
238 |
|
239 def get_groupchat(jid): |
|
240 if type(jid) is types.ListType: |
|
241 jid = jid[1] |
|
242 jid = string.split(unicode(jid), '/')[0] # str(jid) |
|
243 if GROUPCHATS.has_key(jid): |
|
244 return jid |
|
245 else: |
|
246 return None |
|
247 |
|
248 def get_nick(groupchat): |
|
249 try: |
|
250 nicks_string = read_file(NICKS_CACHE_FILE) |
|
251 except: |
|
252 fp = file(NICKS_CACHE_FILE, 'w') |
|
253 fp.write('{}') |
|
254 fp.close() |
|
255 nicks_string = '{}' |
|
256 print 'Initializing ' + NICKS_CACHE_FILE |
|
257 nicks = eval(nicks_string) |
|
258 if nicks.has_key(groupchat): |
|
259 return nicks[groupchat] |
|
260 else: |
|
261 return DEFAULT_NICK |
|
262 |
|
263 def set_nick(groupchat, nick=None): |
|
264 nicks = eval(read_file(NICKS_CACHE_FILE)) |
|
265 if nick: |
|
266 nicks[groupchat] = nick |
|
267 elif groupchat: |
|
268 del nicks[groupchat] |
|
269 fp = file(NICKS_CACHE_FILE, 'w') |
|
270 fp.write(str(nicks)) |
|
271 fp.close() |
|
272 |
|
273 ################################################################################ |
|
274 |
|
275 def get_access_levels(): |
|
276 global ACCESS |
|
277 initialize_file(ACCESS_FILE, '{}') |
|
278 ACCESS = eval(read_file(ACCESS_FILE)) |
|
279 for jid in ADMINS: |
|
280 change_access_perm(jid, 100) |
|
281 for jid in ACCESS.keys(): |
|
282 if ACCESS[jid] == 0: |
|
283 del ACCESS[jid] |
|
284 write_file(ACCESS_FILE , str(ACCESS)) |
|
285 |
|
286 def change_access_temp(source, level=0): |
|
287 global ACCESS |
|
288 jid = get_true_jid(source) |
|
289 try: |
|
290 level = int(level) |
|
291 except: |
|
292 level = 0 |
|
293 ACCESS[jid] = level |
|
294 |
|
295 def change_access_perm(source, level=0): |
|
296 global ACCESS |
|
297 jid = get_true_jid(source) |
|
298 try: |
|
299 level = int(level) |
|
300 except: |
|
301 level = 0 |
|
302 temp_access = eval(read_file(ACCESS_FILE)) |
|
303 temp_access[jid] = level |
|
304 write_file(ACCESS_FILE, str(temp_access)) |
|
305 ACCESS[jid] = level |
|
306 |
|
307 def user_level(source): |
|
308 global ACCESS |
|
309 jid = get_true_jid(source) |
|
310 if ACCESS.has_key(jid): |
|
311 return ACCESS[jid] |
|
312 else: |
|
313 return 0 |
|
314 |
|
315 def has_access(source, required_level): |
|
316 jid = get_true_jid(source) |
|
317 if user_level(jid) >= required_level: |
|
318 return 1 |
|
319 return 0 |
|
320 |
|
321 ################################################################################ |
|
322 |
|
323 def join_groupchat(groupchat, nick=None): |
|
324 if nick: |
|
325 set_nick(groupchat, nick) |
|
326 else: |
|
327 nick = get_nick(groupchat) |
|
328 JCON.send(xmpp.Presence(groupchat + '/' + nick)) |
|
329 if not GROUPCHATS.has_key(groupchat): |
|
330 GROUPCHATS[groupchat] = {} |
|
331 write_file(GROUPCHAT_CACHE_FILE, str(GROUPCHATS.keys())) |
|
332 |
|
333 def leave_groupchat(groupchat): |
|
334 JCON.send(xmpp.Presence(groupchat, 'unavailable')) |
|
335 if GROUPCHATS.has_key(groupchat): |
|
336 del GROUPCHATS[groupchat] |
|
337 write_file(GROUPCHAT_CACHE_FILE, str(GROUPCHATS.keys())) |
|
338 |
|
339 def msg(target, body): |
|
340 msg = xmpp.Message(target, body) |
|
341 if GROUPCHATS.has_key(target): |
|
342 msg.setType('groupchat') |
|
343 else: |
|
344 msg.setType('chat') |
|
345 JCON.send(msg) |
|
346 call_outgoing_message_handlers(target, body) |
|
347 |
|
348 def smsg(type, source, body): |
|
349 if type == 'public': |
|
350 msg(source[1], source[2] + ': ' + body) |
|
351 elif type == 'private': |
|
352 msg(source[0], body) |
|
353 |
|
354 def isadmin(jid): |
|
355 admin_list = ADMINS |
|
356 if type(jid) is types.ListType: |
|
357 jid = jid[0] |
|
358 jid = str(jid) |
|
359 stripped_jid = string.split(jid, '/', 1)[0] |
|
360 resource = '' |
|
361 if len(string.split(jid, '/', 1)) == 2: |
|
362 resource = string.split(jid, '/', 1)[1] |
|
363 if stripped_jid in admin_list: |
|
364 return 1 |
|
365 elif GROUPCHATS.has_key(stripped_jid): |
|
366 if GROUPCHATS[stripped_jid].has_key(resource): |
|
367 if string.split(GROUPCHATS[stripped_jid][resource]['jid'], '/', 1)[0] in admin_list: |
|
368 return 1 |
|
369 return 0 |
|
370 |
|
371 ################################################################################ |
|
372 |
|
373 def messageCB(con, msg): |
|
374 msgtype = msg.getType() |
|
375 body = msg.getBody() |
|
376 fromjid = msg.getFrom() |
|
377 command = '' |
|
378 parameters = '' |
|
379 if body and string.split(body): |
|
380 command = string.lower(string.split(body)[0]) |
|
381 if body.count(' '): |
|
382 parameters = body[(body.find(' ') + 1):] |
|
383 if not msg.timestamp: |
|
384 if msgtype == 'groupchat': |
|
385 call_message_handlers('public', [fromjid, fromjid.getStripped(), fromjid.getResource()], body) |
|
386 if command in COMMANDS: |
|
387 call_command_handlers(command, 'public', [fromjid, fromjid.getStripped(), fromjid.getResource()], parameters) |
|
388 else: |
|
389 call_message_handlers('private', [fromjid, fromjid.getStripped(), fromjid.getResource()], body) |
|
390 if command in COMMANDS: |
|
391 call_command_handlers(command, 'private', [fromjid, fromjid.getStripped(), fromjid.getResource()], parameters) |
|
392 for x_node in msg.getTags('x', {}, 'jabber:x:conference'): |
|
393 inviter_jid = None |
|
394 muc_inviter_tag = msg.getTag('x', {}, 'http://jabber.org/protocol/muc#user') |
|
395 if muc_inviter_tag: |
|
396 if muc_inviter_tag.getTag('invite'): |
|
397 if muc_inviter_tag.getTag('invite').getAttr('from'): |
|
398 inviter_jid = xmpp.JID(muc_inviter_tag.getTag('invite').getAttr('from')) |
|
399 if not inviter_jid: |
|
400 inviter_jid = fromjid |
|
401 call_groupchat_invite_handlers([inviter_jid, inviter_jid.getStripped(), inviter_jid.getResource()], x_node.getAttr('jid'), body) |
|
402 |
|
403 def presenceCB(con, prs): |
|
404 call_presence_handlers(prs) |
|
405 type = prs.getType() |
|
406 groupchat = prs.getFrom().getStripped() |
|
407 nick = prs.getFrom().getResource() |
|
408 |
|
409 if groupchat in GROUPCHATS: |
|
410 if type == 'available' or type == None: |
|
411 if not GROUPCHATS[groupchat].has_key(nick): |
|
412 GROUPCHATS[groupchat][nick] = {'jid': prs.getFrom(), 'idle': time.time()} |
|
413 call_join_handlers(groupchat, nick) |
|
414 time.sleep(0.5) |
|
415 elif type == 'unavailable': |
|
416 if GROUPCHATS[groupchat].has_key(nick): |
|
417 call_leave_handlers(groupchat, nick) |
|
418 del GROUPCHATS[groupchat][nick] |
|
419 elif type == 'error': |
|
420 try: |
|
421 code = prs.asNode().getTag('error').getAttr('code') |
|
422 except: |
|
423 code = None |
|
424 if code == '409': # name conflict |
|
425 join_groupchat(groupchat, nick + '_') |
|
426 time.sleep(0.5) |
|
427 |
|
428 def iqCB(con, iq): |
|
429 call_iq_handlers(iq) |
|
430 |
|
431 def dcCB(): |
|
432 print 'DISCONNECTED' |
|
433 if AUTO_RESTART: |
|
434 print 'WAITING FOR RESTART...' |
|
435 time.sleep(240) # sleep for 240 seconds |
|
436 print 'RESTARTING' |
|
437 os.execl(sys.executable, sys.executable, sys.argv[0]) |
|
438 else: |
|
439 sys.exit(0) |
|
440 |
|
441 ################################################################################ |
|
442 |
|
443 def start(): |
|
444 global JCON |
|
445 JCON = xmpp.Client(server=SERVER, port=PORT, debug=[]) |
|
446 |
|
447 get_access_levels() |
|
448 load_plugins() |
|
449 load_initscript() |
|
450 |
|
451 if JCON.connect(): |
|
452 print "Connected" |
|
453 else: |
|
454 print "Couldn't connect" |
|
455 sys.exit(1) |
|
456 |
|
457 if JCON.auth(USERNAME, PASSWORD, RESOURCE): |
|
458 print 'Logged In' |
|
459 else: |
|
460 print "eek -> ", JCON.lastErr, JCON.lastErrCode |
|
461 sys.exit(1) |
|
462 |
|
463 JCON.RegisterHandler('message', messageCB) |
|
464 JCON.RegisterHandler('presence', presenceCB) |
|
465 JCON.RegisterHandler('iq', iqCB) |
|
466 JCON.RegisterDisconnectHandler(dcCB) |
|
467 JCON.UnregisterDisconnectHandler(JCON.DisconnectHandler) |
|
468 |
|
469 JCON.getRoster() |
|
470 JCON.sendInitPresence() |
|
471 print 'Presence Sent' |
|
472 |
|
473 initialize_file(GROUPCHAT_CACHE_FILE, '[]') |
|
474 groupchats = eval(read_file(GROUPCHAT_CACHE_FILE)) |
|
475 for groupchat in groupchats: |
|
476 join_groupchat(groupchat) |
|
477 time.sleep(0.5) |
|
478 |
|
479 while 1: |
|
480 JCON.Process(10) |
|
481 |
|
482 if __name__ == "__main__": |
|
483 try: |
|
484 start() |
|
485 except KeyboardInterrupt: |
|
486 print '\nINTERUPT' |
|
487 sys.exit(1) |
|
488 except: |
|
489 if AUTO_RESTART: |
|
490 if sys.exc_info()[0] is not SystemExit: |
|
491 traceback.print_exc() |
|
492 try: |
|
493 JCON.disconnected() |
|
494 except IOError: |
|
495 # IOError is raised by default DisconnectHandler |
|
496 pass |
|
497 try: |
|
498 time.sleep(3) |
|
499 except KeyboardInterrupt: |
|
500 print '\nINTERUPT' |
|
501 sys.exit(1) |
|
502 print 'RESTARTING' |
|
503 os.execl(sys.executable, sys.executable, sys.argv[0]) |
|
504 else: |
|
505 raise |
|
506 |
|
507 #EOF |