neutron.py
author Mikael Berthe <mikael@lilotux.net>
Sat, 19 May 2007 15:22:09 +0200
changeset 38 a81dc352e96e
parent 7 766d1c7289ce
permissions -rwxr-xr-x
rss_plugin: remove trailing whitespace

#! /usr/bin/env python

import sys
import os
os.chdir(os.path.dirname(sys.argv[0]))

sys.path.insert(1, 'modules')

import xmpp
import string
import time
import thread
import random
import types
import traceback
import getopt

################################################################################

CONFIGURATION_FILE = 'dynamic/config.cfg'

GENERAL_CONFIG_FILE = 'config.txt'

fp = open(GENERAL_CONFIG_FILE, 'r')
GENERAL_CONFIG = eval(fp.read())
fp.close()

SERVER = GENERAL_CONFIG['SERVER']
PORT = GENERAL_CONFIG['PORT']
USERNAME = GENERAL_CONFIG['USERNAME']
PASSWORD = GENERAL_CONFIG['PASSWORD']
RESOURCE = GENERAL_CONFIG['RESOURCE']

NICKS_CACHE_FILE = 'dynamic/chatnicks.cfg'
GROUPCHAT_CACHE_FILE = 'dynamic/chatrooms.cfg'
ACCESS_FILE = 'dynamic/access.cfg'
PLUGIN_DIR = 'plugins'

DEFAULT_NICK = GENERAL_CONFIG['DEFAULT_NICK']
ADMINS = GENERAL_CONFIG['ADMINS']
ADMIN_PASSWORD = GENERAL_CONFIG['ADMIN_PASSWORD']

AUTO_RESTART = GENERAL_CONFIG['AUTO_RESTART']

PUBLIC_LOG_DIR = GENERAL_CONFIG['PUBLIC_LOG_DIR']
PRIVATE_LOG_DIR = GENERAL_CONFIG['PRIVATE_LOG_DIR']

INITSCRIPT_FILE = GENERAL_CONFIG['INITSCRIPT_FILE']


################################################################################

COMMANDS = {}

GROUPCHATS = {}

MESSAGE_HANDLERS = []
OUTGOING_MESSAGE_HANDLERS = []
JOIN_HANDLERS = []
LEAVE_HANDLERS = []
IQ_HANDLERS = []
PRESENCE_HANDLERS = []
GROUPCHAT_INVITE_HANDLERS = []

COMMAND_HANDLERS = {}

ACCESS = {}

JCON = None

CONFIGURATION = {}

################################################################################

optlist, args = getopt.getopt(sys.argv[1:], '', ['pid='])
for opt_tuple in optlist:
	if opt_tuple[0] == '--pid':
		pid_filename = opt_tuple[1]
		fp = open(pid_filename, 'w')
		fp.write(str(os.getpid()))
		fp.close()

################################################################################

def initialize_file(filename, data=''):
	if not os.access(filename, os.F_OK):
		fp = file(filename, 'w')
		if data:
			fp.write(data)
		fp.close()

def read_file(filename):
	fp = file(filename)
	data = fp.read()
	fp.close()
	return data

def write_file(filename, data):
	fp = file(filename, 'w')
	fp.write(data)
	fp.close()

################################################################################

initialize_file(CONFIGURATION_FILE, '{}')
try:
	CONFIGURATION = eval(read_file(CONFIGURATION_FILE))
except:
	print 'Error Parsing Configuration File'

def config_get(category, key):
	if CONFIGURATION.has_key(category):
		if CONFIGURATION[category].has_key(key):
			return CONFIGURATION[category][key]
		else:
			return None
	else:
		return None

def config_set(category, key, value):
	if not CONFIGURATION.has_key(category):
		CONFIGURATION[category] = {}
	CONFIGURATION[category][key] = value
	config_string = '{\n'
	for category in CONFIGURATION.keys():
		config_string += repr(category) + ':\n'
		for key in CONFIGURATION[category].keys():
			config_string += '\t' + repr(key) + ': ' + repr(CONFIGURATION[category][key]) + '\n'
		config_string += '\n'
	config_string += '}'
	write_file(CONFIGURATION_FILE, config_string)

################################################################################

def register_message_handler(instance):
	MESSAGE_HANDLERS.append(instance)
def register_outgoing_message_handler(instance):
	OUTGOING_MESSAGE_HANDLERS.append(instance)
def register_join_handler(instance):
	JOIN_HANDLERS.append(instance)
def register_leave_handler(instance):
	LEAVE_HANDLERS.append(instance)
def register_iq_handler(instance):
	IQ_HANDLERS.append(instance)
def register_presence_handler(instance):
	PRESENCE_HANDLERS.append(instance)
def register_groupchat_invite_handler(instance):
	GROUPCHAT_INVITE_HANDLERS.append(instance)

def register_command_handler(instance, command, access=0, description='', syntax='', examples=[]):
	COMMAND_HANDLERS[command] = instance
	COMMANDS[command] = {'access': access, 'description': description, 'syntax': syntax, 'examples': examples}

def call_message_handlers(type, source, body):
	for handler in MESSAGE_HANDLERS:
		thread.start_new(handler, (type, source, body))
def call_outgoing_message_handlers(target, body):
	for handler in OUTGOING_MESSAGE_HANDLERS:
		thread.start_new(handler, (target, body))
def call_join_handlers(groupchat, nick):
	for handler in JOIN_HANDLERS:
		thread.start_new(handler, (groupchat, nick))
def call_leave_handlers(groupchat, nick):
	for handler in LEAVE_HANDLERS:
		thread.start_new(handler, (groupchat, nick))
def call_iq_handlers(iq):
	for handler in IQ_HANDLERS:
		thread.start_new(handler, (iq,))
def call_presence_handlers(prs):
	for handler in PRESENCE_HANDLERS:
		thread.start_new(handler, (prs,))
def call_groupchat_invite_handlers(source, groupchat, body):
	for handler in GROUPCHAT_INVITE_HANDLERS:
		thread.start_new(handler, (source, groupchat, body))

def call_command_handlers(command, type, source, parameters):
	if COMMAND_HANDLERS.has_key(command):
		if has_access(source, COMMANDS[command]['access']):
			thread.start_new(COMMAND_HANDLERS[command], (type, source, parameters))
		else:
			smsg(type, source, 'Unauthorized')

################################################################################

def find_plugins():
	valid_plugins = []
	possibilities = os.listdir('plugins')
	for possibility in possibilities:
		if possibility[-3:].lower() == '.py':
			try:
				fp = file(PLUGIN_DIR + '/' + possibility)
				data = fp.read(20)
				if data == '#$ neutron_plugin 01':
					valid_plugins.append(possibility)
			except:
				pass
	return valid_plugins

def load_plugins():
	valid_plugins = find_plugins()
	for valid_plugin in valid_plugins:
		try:
			print 'Plugin: ' + valid_plugin
			#execfile(PLUGIN_DIR + '/' + valid_plugin)
			fp = file(PLUGIN_DIR + '/' + valid_plugin)
			exec fp in globals()
			fp.close()
		except:
			raise

def load_initscript():
	print 'Executing Init Script'
	fp = file(INITSCRIPT_FILE)
	exec fp in globals()
	fp.close()


################################################################################

def get_true_jid(jid):
	true_jid = ''
	if type(jid) is types.ListType:
		jid = jid[0]
	if type(jid) is types.InstanceType:
		jid = unicode(jid) # str(jid)
	stripped_jid = string.split(jid, '/', 1)[0]
	resource = ''
	if len(string.split(jid, '/', 1)) == 2:
		resource = string.split(jid, '/', 1)[1]
	if GROUPCHATS.has_key(stripped_jid):
		if GROUPCHATS[stripped_jid].has_key(resource):
			true_jid = string.split(unicode(GROUPCHATS[stripped_jid][resource]['jid']), '/', 1)[0]
		else:
			true_jid = stripped_jid
	else:
		true_jid = stripped_jid
	return true_jid

def get_groupchat(jid):
	if type(jid) is types.ListType:
		jid = jid[1]
	jid = string.split(unicode(jid), '/')[0] # str(jid)
	if GROUPCHATS.has_key(jid):
		return jid
	else:
		return None

def get_nick(groupchat):
	try:
		nicks_string = read_file(NICKS_CACHE_FILE)
	except:
		fp = file(NICKS_CACHE_FILE, 'w')
		fp.write('{}')
		fp.close()
		nicks_string = '{}'
		print 'Initializing ' + NICKS_CACHE_FILE
	nicks = eval(nicks_string)
	if nicks.has_key(groupchat):
		return nicks[groupchat]
	else:
		return DEFAULT_NICK

def set_nick(groupchat, nick=None):
	nicks = eval(read_file(NICKS_CACHE_FILE))
	if nick:
		nicks[groupchat] = nick
	elif groupchat:
		del nicks[groupchat]
	fp = file(NICKS_CACHE_FILE, 'w')
	fp.write(str(nicks))
	fp.close()

################################################################################

def get_access_levels():
	global ACCESS
	initialize_file(ACCESS_FILE, '{}')
	ACCESS = eval(read_file(ACCESS_FILE))
	for jid in ADMINS:
		change_access_perm(jid, 100)
	for jid in ACCESS.keys():
		if ACCESS[jid] == 0:
			del ACCESS[jid]
	write_file(ACCESS_FILE , str(ACCESS))

def change_access_temp(source, level=0):
	global ACCESS
	jid = get_true_jid(source)
	try:
		level = int(level)
	except:
		level = 0
	ACCESS[jid] = level

def change_access_perm(source, level=0):
	global ACCESS
	jid = get_true_jid(source)
	try:
		level = int(level)
	except:
		level = 0
	temp_access = eval(read_file(ACCESS_FILE))
	temp_access[jid] = level
	write_file(ACCESS_FILE, str(temp_access))
	ACCESS[jid] = level

def user_level(source):
	global ACCESS
	jid = get_true_jid(source)
	if ACCESS.has_key(jid):
		return ACCESS[jid]
	else:
		return 0

def has_access(source, required_level):
	jid = get_true_jid(source)
	if user_level(jid) >= required_level:
		return 1
	return 0

################################################################################

def join_groupchat(groupchat, nick=None):
	if nick:
		set_nick(groupchat, nick)
	else:
		nick = get_nick(groupchat)
	JCON.send(xmpp.Presence(groupchat + '/' + nick))
	if not GROUPCHATS.has_key(groupchat):
		GROUPCHATS[groupchat] = {}
		write_file(GROUPCHAT_CACHE_FILE, str(GROUPCHATS.keys()))

def leave_groupchat(groupchat):
	JCON.send(xmpp.Presence(groupchat, 'unavailable'))
	if GROUPCHATS.has_key(groupchat):
		del GROUPCHATS[groupchat]
		write_file(GROUPCHAT_CACHE_FILE, str(GROUPCHATS.keys()))

def msg(target, body):
	msg = xmpp.Message(target, body)
	if GROUPCHATS.has_key(target):
		msg.setType('groupchat')
	else:
		msg.setType('chat')
	JCON.send(msg)
	call_outgoing_message_handlers(target, body)

def smsg(type, source, body):
	if type == 'public':
		msg(source[1], source[2] + ': ' + body)
	elif type == 'private':
		msg(source[0], body)

def isadmin(jid):
	admin_list = ADMINS
	if type(jid) is types.ListType:
		jid = jid[0]
	jid = str(jid)
	stripped_jid = string.split(jid, '/', 1)[0]
	resource = ''
	if len(string.split(jid, '/', 1)) == 2:
		resource = string.split(jid, '/', 1)[1]
	if stripped_jid in admin_list:
		return 1
	elif GROUPCHATS.has_key(stripped_jid):
		if GROUPCHATS[stripped_jid].has_key(resource):
			if string.split(GROUPCHATS[stripped_jid][resource]['jid'], '/', 1)[0] in admin_list:
				return 1
	return 0

################################################################################

def messageCB(con, msg):
	msgtype = msg.getType()
	body = msg.getBody()
	fromjid = msg.getFrom()
	command = ''
	parameters = ''
	if body and string.split(body):
		command = string.lower(string.split(body)[0])
		if body.count(' '):
			parameters = body[(body.find(' ') + 1):]
		if command == 'mcbot:' and parameters > 1:
			command = string.lower(string.split(body)[1])
			if parameters.count(' '):
				parameters = parameters[(parameters.find(' ') + 1):]
			else:
				parameters = ''
                elif msgtype == 'groupchat' and command == 'help':
                        command = '' # Let's ignore implict help requests in MUC rooms
	if not msg.timestamp:
		if msgtype == 'groupchat':
				call_message_handlers('public', [fromjid, fromjid.getStripped(), fromjid.getResource()], body)
				if command in COMMANDS:
					call_command_handlers(command, 'public', [fromjid, fromjid.getStripped(), fromjid.getResource()], parameters)
		else:
			call_message_handlers('private', [fromjid, fromjid.getStripped(), fromjid.getResource()], body)
			if command in COMMANDS:
				call_command_handlers(command, 'private', [fromjid, fromjid.getStripped(), fromjid.getResource()], parameters)
	for x_node in msg.getTags('x', {}, 'jabber:x:conference'):
		inviter_jid = None
		muc_inviter_tag = msg.getTag('x', {}, 'http://jabber.org/protocol/muc#user')
		if muc_inviter_tag:
			if muc_inviter_tag.getTag('invite'):
				if muc_inviter_tag.getTag('invite').getAttr('from'):
					inviter_jid = xmpp.JID(muc_inviter_tag.getTag('invite').getAttr('from'))
		if not inviter_jid:
			inviter_jid = fromjid
		call_groupchat_invite_handlers([inviter_jid, inviter_jid.getStripped(), inviter_jid.getResource()], x_node.getAttr('jid'), body)

def presenceCB(con, prs):
	call_presence_handlers(prs)
	type = prs.getType()
	groupchat = prs.getFrom().getStripped()
	nick = prs.getFrom().getResource()

	if groupchat in GROUPCHATS:
		if type == 'available' or type == None:
			if not GROUPCHATS[groupchat].has_key(nick):
				GROUPCHATS[groupchat][nick] = {'jid': prs.getFrom(), 'idle': time.time()}
				call_join_handlers(groupchat, nick)
				time.sleep(0.5)
		elif type == 'unavailable':
			if GROUPCHATS[groupchat].has_key(nick):
				call_leave_handlers(groupchat, nick)
				del GROUPCHATS[groupchat][nick]
		elif type == 'error':
			try:
				code = prs.asNode().getTag('error').getAttr('code')
			except:
				code = None
			if code == '409': # name conflict
				join_groupchat(groupchat, nick + '_')
				time.sleep(0.5)

def iqCB(con, iq):
	call_iq_handlers(iq)

def dcCB():
	print 'DISCONNECTED'
	if AUTO_RESTART:
		print 'WAITING FOR RESTART...'
		time.sleep(240) # sleep for 240 seconds
		print 'RESTARTING'
		os.execl(sys.executable, sys.executable, sys.argv[0])
	else:
		sys.exit(0)

################################################################################

def start():
	global JCON
	JCON = xmpp.Client(server=SERVER, port=PORT, debug=[])

	get_access_levels()
	load_plugins()
	load_initscript()

	if JCON.connect():
		print "Connected"
	else:
		print "Couldn't connect"
		sys.exit(1)

	if JCON.auth(USERNAME, PASSWORD, RESOURCE):
		print 'Logged In'
	else:
		print "eek -> ", JCON.lastErr, JCON.lastErrCode
		sys.exit(1)

	JCON.RegisterHandler('message', messageCB)
	JCON.RegisterHandler('presence', presenceCB)
	JCON.RegisterHandler('iq', iqCB)
	JCON.RegisterDisconnectHandler(dcCB)
	JCON.UnregisterDisconnectHandler(JCON.DisconnectHandler)

	JCON.getRoster()
	JCON.sendInitPresence()
	print 'Presence Sent'

	initialize_file(GROUPCHAT_CACHE_FILE, '[]')
	groupchats = eval(read_file(GROUPCHAT_CACHE_FILE))
	for groupchat in groupchats:
		join_groupchat(groupchat)
		time.sleep(0.5)

	while 1:
		JCON.Process(10)

if __name__ == "__main__":
	try:
		start()
	except KeyboardInterrupt:
		print '\nINTERUPT'
		sys.exit(1)
	except:
		if AUTO_RESTART:
			if sys.exc_info()[0] is not SystemExit:
				traceback.print_exc()
			try:
				JCON.disconnected()
			except IOError:
				# IOError is raised by default DisconnectHandler
				pass
			try:
				time.sleep(3)
			except KeyboardInterrupt:
				print '\nINTERUPT'
				sys.exit(1)
			print 'RESTARTING'
			os.execl(sys.executable, sys.executable, sys.argv[0])
		else:
			raise

#EOF