# jsb/plugs/socket/observium.py
#
#
# interact with a mailbox which is configured to receive observium notices
# (c) Lammert 'Fugu' Hellinga - http://www.ar-ix.net
# BSD License
#
# CHANGELOG
#  2011-05-16
#   * initial version
#

""" monitor an observium alert-mailbox """

__version__ = '2011051601'


## jsb imports

from jsb.utils.exception import handle_exception
from jsb.lib.commands import cmnds
from jsb.lib.examples import examples
from jsb.lib.datadir import getdatadir
from jsb.lib.fleet import fleet
from jsb.utils.pdod import Pdod
from jsb.lib.persistconfig import PersistConfig
from jsb.lib.threads import start_new_thread

## basic imports

import os
import time
import imaplib
import email

## defines

cfg = PersistConfig()
cfg.define('imap-host', 'mail.example.com')
cfg.define('imap-username', 'observ@example.com')
cfg.define('imap-password', 'password')
cfg.define('imap-folder', 'INBOX')
cfg.define('imap-ssl', True)
cfg.define('imap-interval', 60)
cfg.define('watcher-enabled', False)

## create an errorclass

class OBSError(Exception): pass

## create our watcher

class OBSWatcher(Pdod):
	def __init__(self):
		Pdod.__init__(self, os.path.join(getdatadir() + os.sep + 'plugs' + os.sep + 'jsb.plugs.sockets.observium', 'observium'))
		self.running = False
		obs = OBSMail()
		self.lastmessage = obs.get_max_id()

	def add(self, bot, ievent):
		if not self.has_key2(bot.cfg.name, ievent.channel):
			self.set(bot.cfg.name, ievent.channel, True)
			self.save()
	
	def remove(self, bot, ievent):
		if self.has_key2(bot.cfg.name, ievent.channel):
			del self.data[bot.cfg.name][ievent.channel]
			self.save()
	
	def start(self):
		self.running = True
		start_new_thread(self.watch, ())

	def stop(self):
		self.running = False

	def watch(self):
		if not cfg.get('watcher-enabled'):
			raise OBSError('watcher not enabled, use "!%s-cfg watcher-enabled True" to enable' % os.path.basename(__file__)[:-3])
		while self.running:
			if self.data:
				try:
					obs = OBSMail()
					message_id = int(obs.get_max_id())
					if message_id != self.lastmessage:
						messages = obs.get_subjects_range(message_id, self.lastmessage)
						self.lastmessage = message_id
						self.announce(messages)
				except:
					pass
				time.sleep(cfg.get('imap-interval'))
	
	def announce(self, messages):
		if not self.running or not cfg.get('watcher-enabled'):
			return
		for name in self.data.keys():
			bot = fleet.byname(name)
			if bot:
				for channel in self.data[name].keys():
					for i in messages:
						bot.say(channel, i)


## create the observium mail class

class OBSMail():
	## opening the connection
	def open_connection(self):
		# Connect to the server
		if cfg.get('imap-ssl'):
			connection = imaplib.IMAP4_SSL(cfg.get('imap-host'))
		else:
			connection = imaplib.IMAP4(cfg.get('imap-host'))
	
		# Login to our account
		connection.login(cfg.get('imap-username'), cfg.get('imap-password'))
		return connection

	## check mailbox
	def observ(self):
		""" open the mailbox and check for messages """
		responses = []

		try:
			c = self.open_connection()
			c.select(cfg.get('imap-folder'), readonly=True)
	
			typ, data = c.search(None, 'UNSEEN')
			for num in reversed(data[0].split()):
				typ, data = c.fetch(num, '(RFC822)')
				for response_part in data:
					if isinstance(response_part, tuple):
						msg = email.message_from_string(response_part[1])
						responses.append('[#%s] %s' % (num, msg['subject']))
		except:
			pass
		finally:
			try:
				c.close()
			except:
				pass
			c.logout()
			return responses


	def get_subjects_range(self, high, low):
		""" open the mailbox and get a list of subjects """
		responses = []

		try:
			c = self.open_connection()
			c.select(cfg.get('imap-folder'), readonly=True)

			for num in xrange(high, low, -1):
				typ, data = c.fetch(num, '(RFC822)')
				for response_part in data:
					if isinstance(response_part, tuple):
						msg = email.message_from_string(response_part[1])
						responses.append('[#%s] %s' % (num, msg['subject']))
		except:
			pass
		finally:
			try:
				c.close()
			except:
				pass
			c.logout()
			return responses
	

	def get_max_id(self):
		""" open the mailbox and return the max message_id """
		max_message_id = 0
		
		try:
			c = self.open_connection()
			c.select(cfg.get('imap-folder'), readonly=True)

			typ, data = c.search(None, 'ALL')
			m_id = max(data[0].split())
			max_message_id = m_id[0]
		except:
			pass
		finally:
			try:
				c.close()
			except:
				pass
			c.logout()
			return max_message_id
	

	def getmessage(self, message_id):
		""" open the mailbox and output the requested message """
		message = 0
		responses = []

		try:
			message = int(message_id)
		except:
			pass
		
		if not message:
			responses.append('Invalid message-id')
			return responses
		
		try:
			c = self.open_connection()
			c.select(cfg.get('imap-folder'), readonly=True)
	
			typ, data = c.fetch(message, '(RFC822)')
			for response_part in data:
				if isinstance(response_part, tuple):
					msg = email.message_from_string(response_part[1])
					responses.append('[Subject]: %s' % (msg['subject']))
					responses.append('[Message]: %s' % (msg.get_payload()))
		except:
			pass
		finally:
			try:
				c.close()
			except:
				pass
			c.logout()
			return responses

## init
watcher = OBSWatcher()
obs = OBSMail()

if not watcher.data:
	watcher = OBSWatcher()

def init():
	if cfg.get('watcher-enabled'):
		watcher.start()
	return 1

def shutdown():
	if watcher.running:
		watcher.stop()
	return 1

def handle_observ(bot, ievent):
	messages = obs.observ()

	for i in messages:
		ievent.reply(i)

def handle_observ_getmessage(bot, ievent):
	message = obs.getmessage(ievent.args[0])

	for i in message:
		ievent.reply(i)

cmnds.add('observ', handle_observ, ['OPER', 'OBSERV'], threaded=True)
examples.add('observ', 'check the observium mailbox for messages.', 'observ')

cmnds.add('observ-msg', handle_observ_getmessage, ['OPER', 'OBSERV'], threaded=True)
examples.add('observ-msg', 'retrieves the contents of a message', 'observ-msg 42')

## observ-watch-start command
def handle_observ_watch_start(bot, ievent):
	if not cfg.get('watcher-enabled'):
		ievent.reply('watcher not enabled, use "!%s-cfg watcher-enabled True" to enable and reload the plugin' % os.path.basename(__file__)[:-3])
		return
	watcher.add(bot, ievent)
	ievent.reply('ok')

cmnds.add('observ-watch-start', handle_observ_watch_start, ['OPER', 'OBSERV'], threaded=True)

## observ-watch-stop command
def handle_observ_watch_stop(bot, ievent):
	if not cfg.get('watcher-enabled'):
		ievent.reply('watcher not enabled, use "!%s-cfg watcher-enabled True" to enable and reload the plugin' % os.path.basename(__file__)[:-3])
		return
	watcher.remove(bot, ievent)
	ievent.reply('ok')

cmnds.add('observ-watch-stop', handle_observ_watch_stop, ['OPER', 'OBSERV'], threaded=True)


## observ-watch-list command
def handle_observ_watch_list(bot, ievent):
	if not cfg.get('watcher-enabled'):
		ievent.reply('watcher not enabled, use "!%s-cfg watcher-enabled True" to enable and reload the plugin' % os.path.basename(__file__)[:-3])
		return
	result = []
	for name in sorted(watcher.data.keys()):
		if watcher.data[name]:
			result.append('on %s:' % name)
		for channel in sorted(watcher.data[name].keys()):
			result.append(channel)
	if result:
		ievent.reply(' '.join(result))
	else:
		ievent.reply('no watchers running')

cmnds.add('observ-watch-list',  handle_observ_watch_list, ['OPER', 'OBSERV'], threaded=True)


