#!/usr/local/bin/python

"""
  This is GoogleBot v2. The original was written with irclib, which turned out
  to be utter pants.
  This version uses twistedwords which I expect is a lot better.
  The idea behind this was taken from the ircLogBot example that ships with
  twistedwords, however the code is almost completely new (barring a few
  borrowed snippets which were great as-is).

  The bot looks for messages in the channel, written as:
    GoogleBot: <message>
  If the message starts with 'quote' followed by a string, the string is looked
  up as a financial quote, otherwise a Google search is conducted.

  This script takes two arguments, first is the network ID, second is the
  logfile name.

  In future, it will always connect to one admin network, and from there join
  other networks as instructed.
"""

# system imports
import ConfigParser
import csv
import datetime
import os
import pickle
import random
import re
import string
import sys
import time

# twisted imports
from twisted.words.protocols import irc
from twisted.internet import ssl, reactor, protocol
from twisted.python import log
from OpenSSL import SSL

# googlebot imports
import Quote
import googlebothelp
import googlesearch
import googlebotdrinks
import googleboturl
import grouphug
import urbandictionary
import woot
import weather


class MessageLogger:
  """
  An independant logger class (because separation of application
  and protocol logic is a good thing).
  """
  def __init__(self, file):
      self.file = file

  def log(self, message):
      """Write a message to the file."""
      timestamp = time.strftime('[%H:%M:%S]', time.localtime(time.time()))
      self.file.write('%s %s\n' % (timestamp, message))
      self.file.flush()

  def close(self):
      self.file.close()


class GoogleBot(irc.IRCClient):
  """A logging IRC bot."""
  
  networks_config = ConfigParser.ConfigParser()
  networks_config.readfp(open('config/networks.conf'))
  for section in networks_config.sections():
    if section == sys.argv[1]:
      try:
        nickname = networks_config.get(section, 'nickname')
      except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        nickname = 'GoogleBot'
      try:
        username = networks_config.get(section, 'username')
      except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        username = 'GoogleBot'
      try:
        password = networks_config.get(section, 'server_password')
      except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        pass
      try:
        realname = networks_config.get(section, 'realname')
      except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
        realname = 'avleen\'s twisted googlebot'
    
  def connectionMade(self):
    irc.IRCClient.connectionMade(self)
    self.logger = MessageLogger(open(self.factory.filename, 'a'))
    self.logger.log('[connected at %s]' % 
        time.asctime(time.localtime(time.time())))
    self.channels_config_mtime = 0

  def connectionLost(self, reason):
    irc.IRCClient.connectionLost(self, reason)
    self.logger.log('[disconnected at %s]' % 
    time.asctime(time.localtime(time.time())))
    self.logger.close()


  # callbacks for events
  def signedOn(self):
    """Called when bot has succesfully signed on to server."""
    for channel in self.factory.channels:
      self.join(channel)

  def joined(self, channel):
    """This will get called when the bot joins the channel."""
    self.logger.log('[I have joined %s]' % channel)

  def privmsg(self, user, channel, msg):
    """ This will get called when the bot receives a message.
    Preferences for each channel are stored in config/channels.conf
    """
    user = user.split('!', 1)[0]
    self.logger.log('%s: <%s> %s' % (channel, user, msg))
    
    # Load per-channel configs
    if (self.channels_config_mtime == 0
      or channels_config_mtime != os.path.getmtime('config/channels.conf')):
        channels_config = self.LoadChannelConfig()

    # Check to see if they're sending me a private message
    if channel == self.nickname:
      if user == 'Av':
        if msg.startswith('join'):
          self.join(msg.split()[-1])
        if msg.startswith('part'):
          self.part(msg.split()[-1])

      # Otherwise tell the user to play with others :-)
      else:
        msg = 'It isn\'t nice to whisper! Play nice with the group.'
        self.msg(user, msg)
        return

    if (msg.startswith(self.nickname + ': listdrinks')
        or msg.startswith('listdrinks')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'drink')
      if option_enabled == False:
        return
      reload(googlebotdrinks)
      drinkslist = googlebotdrinks.GetDrinks()
      drinkcountmsg = '%s drinks in the database:' % len(drinkslist)
      self.msg(channel, drinkcountmsg)
      for drink in drinkslist:
        self.msg(channel, drink)
      return
    if (msg.startswith(self.nickname + ': adddrink')
        or msg.startswith('adddrink')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'drink')
      if option_enabled == False:
        return
      reload(googlebotdrinks)
      msg = msg.replace(self.nickname + ': ', '')
      drink = msg.split(' ', 1)[1]
      adddrink = googlebotdrinks.AddDrink(drink)
      self.msg(channel, adddrink)
    if (msg.startswith(self.nickname + ': popdrink')
        or msg.startswith('popdrink')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'drink')
      if option_enabled == False:
        return
      reload(googlebotdrinks)
      popdrink = googlebotdrinks.PopDrink()
      self.msg(channel, popdrink)
      return
    if (msg.startswith(self.nickname + ': deldrink')
        or msg.startswith('deldrink ')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'drink')
      if option_enabled == False:
        return
      reload(googlebotdrinks)
      msg = msg.replace(self.nickname + ': ', '')
      drink = msg.split(' ', 1)[1]
      deldrink = googlebotdrinks.DelDrink(drink)
      self.msg(channel, deldrink)
      return
    if (msg.startswith(self.nickname + ': drink')
        or msg.startswith('drink')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'drink')
      if option_enabled == False:
        return
      reload(googlebotdrinks)
      drink = googlebotdrinks.ServeDrink(user)
      self.msg(channel, drink)
      return

    if (msg.startswith(self.nickname + ': quote')
        or msg.startswith('quote ')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'quote')
      if option_enabled == False:
        return
      msg = msg.replace(self.nickname + ': ', '')
      p_quote = re.compile('(quote [A-Z]+$)')
      if p_quote.match(msg):
        reload(Quote)
        symb = msg.split()[-1]
        try:
          quote = Quote.Lookup(symb)
        except ValueError:
          msg = 'Quote data not available for: %s' % symb
          self.msg(channel, msg)
        else:
          quotedate = datetime.datetime.fromtimestamp(quote.time)
          self.msg(channel, 'At %s, the value of %s was USD%s' % \
              (quotedate, symb, quote.value))
          self.msg(channel, 'Today\'s high: %s. Today\'s low: %s. ' \
              'Change since last market opening: %s.' % \
              (quote.high, quote.low, quote.change))
      return

    if (msg.startswith(self.nickname + ': ud') or msg.startswith('ud ')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'ud')
      if option_enabled == False:
        return
      msg = msg.replace(self.nickname + ': ', '')
      query = msg.split(' ', 1)[1]
      try:
        reload(urbandictionary)
        result = urbandictionary.UDLookup(query)
        try:
          result[0]['definition']
        except IndexError:
          msg = 'No results found for %s' % query
          self.msg(channel, msg)
          return
        else:
          for entry in ['0', '1']:
            try:
              max_msgs = 5
              msglist = string.split(result[entry]['definition'], '\n')
              for line in range(max_msgs-1):
                self.msg(channel, msglist[line].encode('latin-1'))
              try:
                msglist[max_msgs]
              except IndexError:
                continue
              else:
                long_err = 'Long result returned. For full result, please \
                see http://urbandictionary.com/define.php?term=%s' % \
                re.compile('\s+').sub('+', query)
                self.msg(channel, long_err)
            except IndexError:
              continue
          return
      except Exception:
        msg = 'There was a problem with your query "%s": %s' % \
            (query, sys.exc_info()[1])
        self.msg(channel, msg)
      return

    help = None
    if (msg.startswith(self.nickname + ': listhelp')
        or msg.startswith('listhelp')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'help')
      if option_enabled == False:
        return
      help = googlebothelp.listhelp()
      return
    if (msg.startswith(self.nickname + ': addhelp')
        or msg.startswith('addhelp')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'help')
      if option_enabled == False:
        return
      msg = msg.replace(self.nickname + ': ', '')
      msg = msg.split(' ', 1)[1]
      help = googlebothelp.addhelp(msg)
      return
    if (msg.startswith(self.nickname + ': pophelp')
        or msg.startswith('pophelp')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'help')
      if option_enabled == False:
        return
      msg = msg.replace(self.nickname + ': ', '')
      help = googlebothelp.pophelp()
      return
    if (msg.startswith(self.nickname + ': delhelp')
        or msg.startswith('delhelp ')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'help')
      if option_enabled == False:
        return
      msg = msg.replace(self.nickname + ': ', '')
      msg = msg.split(' ', 1)[1]
      help = googlebothelp.delhelp(msg)
      return
    if (msg.startswith(self.nickname + ': help')
        or msg.startswith('help')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'help')
      if option_enabled == False:
        return
      try:
        nick = msg.split(' ')[1]
        help = googlebothelp.servehelp(nick)
      except IndexError:
        help = googlebothelp.servehelp()
    if help != None:
      self.msg(channel, help)
      return

    if msg.find('http://') != -1:
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'url')
      if option_enabled == False:
        return
      reload(googleboturl)
      msg = googleboturl.MatchURL(msg)
      if msg != None:
        self.msg(channel, msg)
      return

    if (msg.startswith(self.nickname + ': woot')
        or msg.startswith('woot')):
      option_enabled = self.CheckChannelConfig(channels_config, channel, 'woot')
      if option_enabled == False:
        return
      reload(woot)
      result = woot.Woot()
      self.msg(channel, result.encode('latin-1', 'ignore'))
      return

    if (msg.startswith(self.nickname + ': weather')
        or msg.startswith('weather')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'weather')
      if option_enabled == False:
        return
      reload(weather)
      result = weather.WeatherLookup(msg)
      self.msg(channel, result.encode('latin-1'))
      return

    if (msg.startswith(self.nickname + ': grouphug')
        or msg.startswith('grouphug')):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'grouphug')
      if option_enabled == False:
        return
      reload(grouphug)
      result = grouphug.main()
      self.msg(channel, result.encode('latin-1'))
      return

    if msg.startswith(self.nickname + ':'):
      option_enabled = self.CheckChannelConfig(channels_config, channel,
          'search')
      if option_enabled == False:
        return
      reload(googlesearch)
      query = msg.split(': ', 2)[1]
      searchresult = googlesearch.GoogleSearch(query)
      searchresult = unicode(searchresult, errors='ignore')
      self.msg(channel, searchresult.encode('latin-1'))
      return

  def action(self, user, channel, msg):
    """This will get called when the bot sees someone do an action."""
    user = user.split('!', 1)[0]
    self.logger.log('* %s %s' % (user, msg))

  # irc callbacks
  def irc_NICK(self, prefix, params):
    """Called when an IRC user changes their nickname."""
    old_nick = prefix.split('!')[0]
    new_nick = params[0]
    self.logger.log('%s is now known as %s' % (old_nick, new_nick))

  def LoadChannelConfig(self):
    """ Called to reload the channels configuration file
    """
    channels_config = ConfigParser.ConfigParser()
    channels_config.readfp(open('config/channels.conf'))
    self.channel_config_mtime = os.path.getmtime('config/channels.conf')
    return channels_config

  def CheckChannelConfig(self, channels_config, channel, option):
    try:
      channels_config.get(channel, option)
    except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
      if channels_config.get('default', option) == 'no':
        return False
    else:
      if channels_config.get(channel, option) == 'no':
        return False

class GoogleBotFactory(protocol.ClientFactory):
  """A factory for GoogleBots.
  A new protocol instance will be created each time we connect to the server.
  """

  # the class of the protocol to build when new connection is made
  protocol = GoogleBot

  def __init__(self, channels, filename):
    self.channels = channels
    self.filename = filename

  def clientConnectionLost(self, connector, reason):
    """If we get disconnected, reconnect to server."""
    connector.connect()

  def clientConnectionFailed(self, connector, reason):
    print 'connection failed:', reason
    reactor.stop()


if __name__ == '__main__':
  # initialize logging
  log.startLogging(sys.stdout)

  # parse config files
  networks_config = ConfigParser.ConfigParser()
  networks_config.readfp(open('config/networks.conf'))

  for section in networks_config.sections():
    if section == sys.argv[1]:
      for row in csv.reader([networks_config.get(section, 'channels')]):
        channels = row
      server = networks_config.get(section, 'server')
      port = int(networks_config.get(section, 'port'))
      logfile = networks_config.get(section, 'logfile')
      ssl_connect = networks_config.get(section, 'ssl_connect')

      # create factory protocol and application
      f = GoogleBotFactory(channels, logfile)
      if ssl_connect == 'yes':
        reactor.connectSSL(server, port, f, ssl.ClientContextFactory())
      else:
        reactor.connectTCP(server, port, f)
      print 'Server: %s\nPort: %s' % (server, port)
      break

  # run bot
  reactor.run()
