Newer
Older
# guppy Copyright (C) 2010-2011 guppy team members.
#
# This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
# This is free software, and you are welcome to redistribute it
# under certain conditions; type `show c' for details.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import socket, os, threading, re, asynchat, traceback, ssl
class User(str):
def __init__(self, user):
super(User, self).__init__(self, user)
if getattr(user, "nick", None) is not None:
self.nick = user.nick
self.ident = user.ident
self.host = user.host
else:
self.ident = ""
self.host = ""
class PluginManager(object):
def __init__(self, server):
self.server = server
self.handlers = {
"join" : [ ],
"part" : [ ],
"quit" : [ ],
"message" : [ ],
"connect" : [ ],
"disconnect": [ ],
"action" : [ ],
"nick" : [ ],
"mode" : [ ],
"command" : { },
}
def handle(self, event, handler_func, handler_commands=None):
event = event.lower()
if event == "command":
if handler_commands is not None:
for command in handler_commands:
self.handlers[event][command.lower()] = handler_func
else:
if self.handlers.get(event, None) is None:
self.handlers[event] = []
self.handlers[event].append(handler_func)
def unhandle(self, event, handler_func, handler_commands=None):
event = event.lower()
if event == "command":
handlers = {}
for command, handler in self.handlers[event]:
if handler != handler_func and not command.lower() in handler_commands:
handlers.insert(0, handler)
self.handlers[event] = handlers
else:
handlers = []
for handler in self.handlers[event]:
if handler != handler_func:
handlers.insert(0, handler)
self.handlers[event] = handlers
def event(self, eventName, *args):
eventName = eventName.lower()
if self.handlers.get(eventName, None) is None: return
handler = self.handlers[eventName].get(args[2].lower(), None)
if handler is None:
else:
for handler in self.handlers[eventName]:
def loadedPlugin(self, pluginName):
return pluginName.lower() in self.plugins
def pluginExists(self, pluginName):
return plugins.getPlugin(pluginName.lower()) is not None
def loadPlugin(self, pluginName):
plugins.refresh()
if not self.loadedPlugin(pluginName.lower()):
if not self.pluginExists(pluginName.lower()):
raise ValueError("No such plugin.")
pClass = plugins.getPlugin(pluginName.lower())
self.plugins[pluginName.lower()] = pClass(self.server)
def getPlugin(self, pluginName):
try:
return self.plugins[pluginName.lower()]
except KeyError:
return None
def reloadPlugin(self, pluginName):
self.unloadPlugin(pluginName.lower())
self.loadPlugin(pluginName.lower())
if self.loadedPlugin(pluginName.lower()):
# the plugin is loaded
inst = self.plugins[pluginName.lower()] # the plugin object
del self.plugins[pluginName.lower()] # delete plugins object from self.plugins
for event, eList in list(self.handlers.items()):
FurryHead
committed
destructors = []
destructor = getattr(inst, "destroy", None)
if destructor is not None and destructor not in destructors:
FurryHead
committed
destructors.append(destructor)
else:
newList[cmd] = func
self.handlers[event] = newList
for f in destructors:
f()
FurryHead
committed
destructors = []
destructor = getattr(inst, "destroy", None)
if destructor is not None and destructor not in destructors:
FurryHead
committed
destructors.append(destructor)
else:
newList.append(func)
self.handlers[event] = newList
for f in destructors:
f()
if getattr(inst, "destroy", None) != None: inst.destroy()
plugins = [k for k in self.server.config["plugins"].split(",") if k != '']
self.server.prnt("Loading "+plugin)
self.loadPlugin(plugin.lower())
self.unloadPlugin(plugin.lower())
class IRC(asynchat.async_chat):

Svetlana Tkachenko
committed
def handle_error(self):
'''Print a traceback when an error happens'''
traceback.print_exc()
asynchat.async_chat.__init__(self)
self.pluginManager = PluginManager(self)
self.config = config
self.handle = self.pluginManager.handle
self.unhandle = self.pluginManager.unhandle
self.handlers = self.pluginManager.handlers
self.plugins = self.pluginManager.plugins
self.getPlugin = self.pluginManager.getPlugin
errs = self.pluginManager.loadAllPlugins()
self.userlist = { }

Svetlana Tkachenko
committed
self.data = b""
self.set_terminator(b"\r\n")
# check ipv6 flag (added in a recent version, may not exist)
if ('ipv6' in self.config) and self.config["ipv6"].lower().startswith("y"): stype = socket.AF_INET6
else: stype = socket.AF_INET
# create socket
tup = socket.getaddrinfo(self.config["host"], int(self.config["port"]), stype)[0][4]
self.create_socket(stype, socket.SOCK_STREAM)
# check ssl
if self.config["use_ssl"].lower().startswith("y"):
self.ssl = ssl.wrap_socket(self.socket)
self.set_socket(self.ssl)
# connect
self.connect(tup)
except ssl.SSLError as error:
self.prnt('SSL Error connecting to server. (Are you using the right port?) Error message: %s' % error)
return
except socket.error as error:
self.prnt('There was an error connecting to %s. %s' % (self.config["host"], error))
FurryHead
committed
def _getData(self):
ret = self.data

Svetlana Tkachenko
committed
self.data = b""
return ret
def found_terminator(self):

Svetlana Tkachenko
committed
data = self._getData().decode("utf-8")
words = data.split(" ")
if words[0] == "PING":
# The server pinged us, we need to pong or we'll be disconnected
# (make sure to send whatever follows the PING, in case they send a random hash)
self.sendLine("PONG " + data[5:])
return
# Takes the ':Nick!Ident@Host' chunk and assigns (nick,ident,host) to user
user = None
if words[0].find("!") != -1:
user = User(words[0][words[0].find(":") + 1:words[0].find('!')])
user.ident = words[0][words[0].find('!') + 1:words[0].find('@')]
user.host = words[0][words[0].find('@') + 1:]
else:
#it's our nick, in the format ":NickName"
user = User(words[0][words[0].find(":") + 1:])
if words[1] == "433":
#There was a nick collision
self.config["nickname"] = self.config["nickname"] + "_"
self.sendLine("NICK "+self.config["nickname"])
elif words[1] == "422" or words[1] == "376":
# We successfully logged in, do post-login procedures
if self.config["ns_name"] != "" and self.config["ns_pwd"] != "":
self.doMessage("NickServ", "IDENTIFY "+self.config["ns_name"]+" "+self.config["ns_pwd"])
self.pluginManager.event("connect", self.config["network"])
for chan in [k for k in self.config["channels"].split(",") if k != '']:
self.doJoin(chan)
elif words[1] == "353":
#user list, if it's large, we only got part of it.
words = [k for k in str(data).replace(" =", " ").replace(" @", " ").replace(" +", " ").replace(" %", " ").replace(" &", " ").replace(" @", " ").replace(" ~", " ").split(" ") if k != '']
if self.userlist.get(words[3], None) is None:
self.userlist[words[3]] = []
self.userlist[words[3]] += [u.strip(":") for u in words[3:]]
elif words[1] == "PRIVMSG":
# We got a message
channel = (words[2] if words[2] != self.config["nickname"] else user)
# Checks to see if message is a PM or not
isPrivate = False
if words[2] == self.config["nickname"]:
isPrivate = True
message = data[data.find(":", data.find(channel[(channel.find("-") == -1 and 1 or channel.find("-")):]))+1:]
if message.find("\x01ACTION") == 0:
# String was found, it's an action
self.pluginManager.event("action", channel, user, message[8:-1])
elif message.find(self.config["comchar"]) == 0:
# String was found, it's a command!
args = message.split(" ")
cmd = args[0][len(self.config["comchar"]):]
args.pop(0)
self.pluginManager.event("message", channel, user, message)
self.pluginManager.event("command", channel, user, cmd.lower(), args)
elif isPrivate:
# If its a private message, its probably a command
args = message.split(" ")
cmd = args[0]
args.pop(0)
self.pluginManager.event("message", channel, user, message)
self.pluginManager.event("command", channel, user, cmd.lower(), args)
FurryHead
committed
else:
if re.match("^"+self.config["nickname"]+"[^A-Za-z0-9] .+$", message, flags=re.IGNORECASE):
#Someone addressed us, it's a command (probably)
cmd = args[1]
args = args[2:]
self.pluginManager.event("message", channel, user, message)
self.pluginManager.event("command", channel, user, cmd.lower(), args)
#Nothing was found, it has to be a message
self.pluginManager.event("message", channel, user, message)
elif words[1] == "JOIN":
# Someone joined a channel that we're in
if user != self.config["nickname"]:
self.pluginManager.event("join", words[2].strip(":"), user)
self.userlist[words[2].strip(":")].append(user)
elif words[1] == "PART":
if user != self.config["nickname"]:
# Someone parted a channel we're in
if user in self.userlist[words[2].strip(":")]:
self.userlist[words[2]].remove(user)
self.pluginManager.event("part", words[2].strip(":"), user, " ".join(words[3:]))
elif words[1] == "QUIT":
# Someone quit the server
if user in self.userlist[k]:
self.userlist[k].remove(user)
self.pluginManager.event("quit", user, " ".join(words[2:])[1:])
elif words[1] == "NICK":
# Someone changed their nickname
if user in self.userlist[k]:
self.userlist[k].remove(user)
self.userlist[k].append(words[2].strip(":"))
self.pluginManager.event("nick", user, words[2].strip(":"))
elif words[1] == "MODE":
if user != self.config["nickname"]:
# Someone set a mode
try:
self.pluginManager.event("mode", words[2], user, words[3], words[4])
except IndexError: # words[4] is not valid, it wasn't set on a user
self.pluginManager.event("mode", words[2], user, words[3], "")
elif words[1] == "KICK":
#someone kicked someone
if self.userlist.get(words[2].lower(), None) is not None:
self.userlist[words[2].lower()].remove(words[3])
self.pluginManager.event("kick", words[2], user, words[3], words[4][1:])
elif words[1] == "NOTICE":
self.pluginManager.event("notice", user, words[2], " ".join(words[3:]).strip(":"))
else:
self.pluginManager.event(words[1], data)
def collect_incoming_data(self, data):
self.data += data
def handle_connect(self):
self.config["ident"] = ident = self.config["ident"] if self.config["ident"] != "" else self.config["nickname"]
# pass, nick, user - http://tools.ietf.org/html/rfc1459#section-4.1

Svetlana Tkachenko
committed
if self.config["srpass"] != "": self.sendLine("PASS "+self.config["srpass"])
self.sendLine("NICK "+self.config["nickname"])
self.sendLine("USER "+ident+" * * *")
def handle_disconnect(self):
self.pluginManager.event("disconnect", self.config["network"])
print(time.strftime("%Y-%m-%d %H:%M:%S") + (" ["+self.config["network"]+"] "+line))
#print("'"+line+"'")

Svetlana Tkachenko
committed
self.push(bytes(line, "utf_8"))

Svetlana Tkachenko
committed
self.push(bytes(line + "\r\n", "utf_8"))
def doMessage(self, channel, message):
self.sendLine("PRIVMSG "+channel+" :"+message)
self.pluginManager.event("message", channel, User(self.config["nickname"]), message)
def doAction(self, channel, action):
self.sendLine("PRIVMSG "+channel+" :\x01ACTION "+action+" \x01")
self.pluginManager.event("action", channel, User(self.config["nickname"]), action)
def doQuit(self, message=""):
self.sendLine("QUIT :" + message)
self.pluginManager.event("quit", User(self.config["nickname"]), message)
def doNotice(self, user, message):
self.sendLine("NOTICE "+user+" :"+message)
def doNick(self, newnick):
self.sendLine("NICK " + newnick)
self.pluginManager.event("nick", User(self.config["nickname"]), User(newnick))
self.config["nickname"] = newnick
def doJoin(self, channel):
self.sendLine("JOIN "+channel)
self.pluginManager.event("join", channel, User(self.config["nickname"]))
def doKick(self, channel, user, message=""):
self.sendLine("KICK %s %s :%s" % (channel, user, message))
self.pluginManager.event("kick", channel, User(self.config["nickname"]), user, message)
def doPart(self, channel, message=""):
del self.userlist[channel]
self.pluginManager.event("part", channel, User(self.config["nickname"]), message)
def doMode(self, channel, mode, user=""):
self.sendLine("MODE "+channel+" "+mode+" "+user)
self.pluginManager.event("mode", channel, User(self.config["nickname"]), mode, user)