neutron.py
changeset 0 93b25987d3e5
child 5 0cc11f58b34e
equal deleted inserted replaced
-1:000000000000 0:93b25987d3e5
       
     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