irc.py

Mon, 08 Dec 2014 16:12:51 -0500

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Mon, 08 Dec 2014 16:12:51 -0500
changeset 110
b2770c43b752
parent 94
2c93636202a5
child 112
cdafc1a0544e
permissions
-rw-r--r--

- now in color!

import asyncore
import socket
import sys
import re
import modulecore as ModuleCore
import traceback
from configfile import Config
import bt as Bt
import hgpoll as HgPoll

CLIF_CONNECTED = (1 << 1)

all_clients = []

class RestartError (Exception):
	def __init__ (self, value):
		self.message = value
	def __str__ (self):
		return self.message

#
# Prints a line to log channel(s)
#
def broadcast (line):
	print line
	for client in all_clients:
		if not client.flags & CLIF_CONNECTED:
			continue

		for channel in client.channels:
			if channel.get_value ('logchannel', default=False):
				client.write ("PRIVMSG %s :%s" % (channel.get_value ('name'), line))

class logical_exception (Exception):
	def __init__ (self, value):
		self.value = value
	def __str__ (self):
		return self.value

#
# Main IRC client class
#
class irc_client (asyncore.dispatcher):
	def __init__ (self, cfg, flags):
		global all_clients
		self.name = cfg.get_value ('name')
		self.host = cfg.get_value ('address')
		self.port = cfg.get_value ('port', default=6667)
		self.password = cfg.get_value ('password', default='')
		self.channels = cfg.get_nodelist ('channels')
		self.flags = flags
		self.send_buffer = []
		self.umode = cfg.get_value ('umode', default='')
		self.cfg = cfg
		self.desired_name = Config.get_value ('nickname', default='cobalt')
		self.mynick = self.desired_name
		self.verbose = Config.get_value ('verbose', default=False)
		self.commandprefix = Config.get_value ('commandprefix', default='.')
		all_clients.append (self)
		asyncore.dispatcher.__init__ (self)
		self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
		self.connect ((self.host, self.port))

	def register_to_irc (self):
		ident = Config.get_value ('ident', default='cobalt')
		gecos = Config.get_value ('gecos', default='cobalt')
		self.write ("PASS %s" % self.password)
		self.write ("USER %s * * :%s" % (ident, gecos))
		self.write ("NICK %s" % self.mynick)

	def handle_connect (self):
		print "Connected to [%s] %s:%d" % (self.name, self.host, self.port)
		self.register_to_irc()

	def write (self, utfdata):
		try:
			self.send_buffer.append ("%s" % utfdata.decode("utf-8","ignore").encode("ascii","ignore"))
		except UnicodeEncodeError:
			pass

	def handle_close (self):
		print "Connection to [%s] %s:%d terminated." % (self.name, self.host, self.port)
		self.close()

	def handle_write (self):
		self.send_all_now()

	def readable (self):
		return True

	def writable (self):
		return len (self.send_buffer) > 0

	def send_all_now (self):
		for line in self.send_buffer:
			if self.verbose:
				print "[%s] <- %s" % (self.name, line)
			self.send ("%s\n" % line)
		self.send_buffer = []

	def handle_read (self):
		lines = self.recv (4096).splitlines()
		for utfline in lines:
			try:
				line = utfline.decode("utf-8","ignore").encode("ascii","ignore")
			except UnicodeDecodeError:
				continue

			if self.verbose:
				print "[%s] -> %s" % (self.name, line)

			if line.startswith ("PING :"):
				self.write ("PONG :%s" % line[6:])
			else:
				words = line.split(" ")
				if len(words) >= 2:
					if words[1] == "001":
						self.flags |= CLIF_CONNECTED

						for channel in self.channels:
							self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default='')))

						umode = self.cfg.get_value ('umode', '')

						if umode != '':
							self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', '')))
					elif words[1] == "PRIVMSG":
						self.handle_privmsg (line)
					elif words[1] == 'QUIT':
						rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) QUIT')
						match = rex.match (line)

						# Try reclaim our nickname if possible
						if match and match.group(1) == self.desired_name:
							self.mynick = self.desired_name
							self.write ("NICK %s" % self.mynick)
					elif words[1] == "433":
						#:irc.localhost 433 * cobalt :Nickname is already in use.
						self.mynick += self.cfg.get_value ('conflictsuffix', default='`')
						self.write ("NICK " + self.mynick)

			# Check for new issues on the bugtracker
			Bt.poll()

			# Check for new commits in the repositories
			HgPoll.poll()

	# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
	#
	#	Handle a PRIVMSG line from the IRC server
	#
	def handle_privmsg (self, line):
		rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) PRIVMSG ([^ ]+) :(.+)$')
		match = rex.match (line)

		if not match:
			broadcast ("Recieved bad PRIVMSG: %s" % line)

		sender = match.group (1)
		user = match.group (2)
		host = match.group (3)
		channel = match.group (4)
		message = match.group (5)
		replyto = channel if channel != self.mynick else sender

		# Check for command.
		if len(message) >= 2 and message[0] == self.commandprefix and message[1] != self.commandprefix:
			stuff = message[1:].split(' ')
			command = stuff[0]
			args = stuff[1:]
			self.handle_command (sender, user, host, replyto, command, args, message)
			return

		Bt.process_message (self, line, replyto)

	def add_irc_channel (self, channame):
		for channel in self.channels:
			if channel.get_value ('name').upper() == channame.upper():
				return

		channel = self.cfg.append_nodelist ('channels')
		channel.set_value ('name', channame)
		self.channels = self.cfg.get_nodelist ('channels')
		self.write ('JOIN ' + channame)
		self.cfg.save()

	def remove_irc_channel (self, channame):
		for channel in self.channels:
			if channel.get_value ('name') == channame:
				self.channels.remove (channel)
				break
		else:
			return

		self.write ('PART ' + channame)
		self.cfg.save()

	def handle_command (self, sender, ident, host, replyto, command, args, message):
		kvargs = {'sender': sender, 'ident': ident, 'host': host, 'replyto': replyto, 'cmdname': command, 'message': message}

		try:
			result = ModuleCore.call_command (self, **kvargs)

			if result:
				return
		except ModuleCore.CommandError as e:
			lines = str (e).split ('\n')
			self.privmsg (replyto, 'error: %s' % lines[0])

			for line in lines[1:]:
				self.privmsg (replyto, '  ' + line)
			return

	def handle_error(self):
		raise RestartError (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))

	def restart(self):
		raise RestartError('')

	def privmsg (self, channel, msg):
		self.write ("PRIVMSG %s :%s" % (channel, msg))

	def close_connection (self, message):
		if self.flags & CLIF_CONNECTED:
			self.write ("QUIT :" + message)
		self.send_all_now()
		self.close()

	def quit_irc (self):
		self.close_connection ('Leaving')

	def exceptdie (self):
		self.close_connection ('Caught exception')

	def keyboardinterrupt (self):
		self.close_connection ('KeyboardInterrupt')

mercurial