Beispiel #1
0
	def __init__(self, serverfolder):
		self.logger = logging.getLogger('DideRobot')
		self.logger.info("New bot for server '{}' created".format(serverfolder))

		#Initialize some variables (in init() instead of outside it to prevent object sharing between instances)
		self.serverfolder = serverfolder
		self.ircSocket = None
		self.nickname = None  # Will get set once we connect, when we know if we have the nickname we want
		self.channelsUserList = {}  # Will be a dict with joined channels as keys and a list of users in those channels as values
		self.isUpdatingChannelsUserList = False
		self.isMuted = False

		self.connectedAt = None  # Will be set to the timestamp on which we connect. 'None' means we're not connected
		self.connectionManagerGreenlet = None  # This will get a reference to the greenlet keeping the connection alive. If this ends, the bot is closed down
		self.shouldReconnect = True
		self.reconnectionAttempCount = None  # Will keep a count of how many times we've tried to connect, to see if we've exceeded the limit (if any)
		self.maxConnectionRetries = None  # None means unlimited attempts, can be set by settings file

		self.secondsBetweenLineSends = None  # If it's 'None', there's no rate limiting, otherwise it's a float of seconds between line sends
		self.linesToSend = None  # A list with all the lines to send if there is a queue and rate limiting
		self.lineSendingGreenlet = None  # Will store the greenlet that is currently working its way through the message queue, or is None if there is none

		self.commandPrefix = ""  # Pulled from the settings file, separate variable because it's referenced a lot
		self.commandPrefixLength = 0  # The length if the prefix is also often needed, prevent constant recalculation

		#Load the settings, and only connect to the server if that succeeded
		self.settings = BotSettingsManager(self.serverfolder)
		if self.settings.loadedSuccessfully:
			self.parseSettings()
			self.messageLogger = MessageLogger(self)
			self.connectionManagerGreenlet = gevent.spawn(self.keepServerConnectionAlive)
		else:
			self.logger.error("|{}| Invalid settings file, shutting down".format(self.serverfolder))
			#Also tell the bot manager that we'll stop existing
			GlobalStore.bothandler.unregisterBot(self.serverfolder)
Beispiel #2
0
    def __init__(self, serverfolder):
        self.logger = logging.getLogger('DideRobot')
        self.logger.info(
            "New bot for server '{}' created".format(serverfolder))

        #Initialize some variables (in init() instead of outside it to prevent object sharing between instances)
        self.serverfolder = serverfolder
        self.ircSocket = None
        self.nickname = None  # Will get set once we connect, when we know if we have the nickname we want
        self.channelsUserList = {
        }  # Will be a dict with joined channels as keys and a list of users in those channels as values
        self.isUpdatingChannelsUserList = False
        self.isMuted = False

        self.connectedAt = None  # Will be set to the timestamp on which we connect. 'None' means we're not connected
        self.connectionManagerGreenlet = None  # This will get a reference to the greenlet keeping the connection alive. If this ends, the bot is closed down
        self.shouldReconnect = True
        self.reconnectionAttempCount = None  # Will keep a count of how many times we've tried to connect, to see if we've exceeded the limit (if any)
        self.maxConnectionRetries = None  # None means unlimited attempts, can be set by settings file

        self.secondsBetweenLineSends = None  # If it's 'None', there's no rate limiting, otherwise it's a float of seconds between line sends
        self.linesToSend = None  # A list with all the lines to send if there is a queue and rate limiting
        self.lineSendingGreenlet = None  # Will store the greenlet that is currently working its way through the message queue, or is None if there is none

        self.commandPrefix = ""  # Pulled from the settings file, separate variable because it's referenced a lot
        self.commandPrefixLength = 0  # The length if the prefix is also often needed, prevent constant recalculation

        #Load the settings, and only connect to the server if that succeeded
        self.settings = BotSettingsManager(self.serverfolder)
        if self.settings.loadedSuccessfully:
            self.parseSettings()
            self.messageLogger = MessageLogger(self)
            self.connectionManagerGreenlet = gevent.spawn(
                self.keepServerConnectionAlive)
        else:
            self.logger.error(
                "|{}| Invalid settings file, shutting down".format(
                    self.serverfolder))
            #Also tell the bot manager that we'll stop existing
            GlobalStore.bothandler.unregisterBot(self.serverfolder)
Beispiel #3
0
class BasicBot(irc.IRCClient):
    """A basic IRC bot.

    Simply connects to the channel specified by its factory using the nick
    specified by the factory, logging connection information to the file
    specified by the factory.
    """

    def connectionMade(self):
        irc.IRCClient.connectionMade(self)
        self.setNick(self.factory.nickname)
        self.logger = MessageLogger(open(self.factory.filename, "a"))
        self.logger.log("[connected at %s]" % 
                        time.asctime(time.localtime(time.time())))

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

    def signedOn(self):
        self.join(self.factory.channel)
Beispiel #4
0
class DideRobot(object):
    def __init__(self, serverfolder):
        self.logger = logging.getLogger('DideRobot')
        self.logger.info(
            "New bot for server '{}' created".format(serverfolder))

        #Initialize some variables (in init() instead of outside it to prevent object sharing between instances)
        self.serverfolder = serverfolder
        self.ircSocket = None
        self.nickname = None  # Will get set once we connect, when we know if we have the nickname we want
        self.channelsUserList = {
        }  # Will be a dict with joined channels as keys and a list of users in those channels as values
        self.isUpdatingChannelsUserList = False
        self.isMuted = False

        self.connectedAt = None  # Will be set to the timestamp on which we connect. 'None' means we're not connected
        self.connectionManagerGreenlet = None  # This will get a reference to the greenlet keeping the connection alive. If this ends, the bot is closed down
        self.shouldReconnect = True
        self.reconnectionAttempCount = None  # Will keep a count of how many times we've tried to connect, to see if we've exceeded the limit (if any)
        self.maxConnectionRetries = None  # None means unlimited attempts, can be set by settings file

        self.secondsBetweenLineSends = None  # If it's 'None', there's no rate limiting, otherwise it's a float of seconds between line sends
        self.linesToSend = None  # A list with all the lines to send if there is a queue and rate limiting
        self.lineSendingGreenlet = None  # Will store the greenlet that is currently working its way through the message queue, or is None if there is none

        self.commandPrefix = ""  # Pulled from the settings file, separate variable because it's referenced a lot
        self.commandPrefixLength = 0  # The length if the prefix is also often needed, prevent constant recalculation

        #Load the settings, and only connect to the server if that succeeded
        self.settings = BotSettingsManager(self.serverfolder)
        if self.settings.loadedSuccessfully:
            self.parseSettings()
            self.messageLogger = MessageLogger(self)
            self.connectionManagerGreenlet = gevent.spawn(
                self.keepServerConnectionAlive)
        else:
            self.logger.error(
                "|{}| Invalid settings file, shutting down".format(
                    self.serverfolder))
            #Also tell the bot manager that we'll stop existing
            GlobalStore.bothandler.unregisterBot(self.serverfolder)

    def parseSettings(self):
        """Retrieve some frequently-used values, to store them in a more easily-accessible way"""
        #The command prefix is going to be needed often, as will its length. Put that in an easy-to-reach place
        self.commandPrefix = self.settings['commandPrefix']
        self.commandPrefixLength = len(self.commandPrefix)

        #Load in the maximum connection settings to try, if there is any
        self.maxConnectionRetries = self.settings.get('maxConnectionRetries',
                                                      -1)
        #Assume values smaller than zero mean endless retries
        if self.maxConnectionRetries < 0:
            self.maxConnectionRetries = None

        #Time in seconds (can have decimals) between line sends, because some servers are rate limited
        self.secondsBetweenLineSends = self.settings.get(
            'minSecondsBetweenMessages', -1)
        if self.secondsBetweenLineSends <= 0:
            self.secondsBetweenLineSends = None

    def reloadSettings(self):
        self.settings.reloadSettings(True)
        if self.settings.loadedSuccessfully:
            self.parseSettings()
            return True
        else:
            return False

    #CONNECTION FUNCTIONS
    def keepServerConnectionAlive(self):
        while True:
            # Open a connection
            self.ircSocket = gevent.socket.socket(gevent.socket.AF_INET,
                                                  gevent.socket.SOCK_STREAM)
            self.ircSocket.settimeout(
                20.0
            )  #Set the timout to establishing a connection, gets increased when we successfully connect
            self.logger.info("Connecting to {} ({} on port {})".format(
                self.serverfolder, self.settings['server'],
                self.settings['port']))
            try:
                self.ircSocket.connect(
                    (self.settings['server'], self.settings['port']))
            except (gevent.socket.timeout, gevent.socket.error,
                    gevent.socket.herror, gevent.socket.gaierror) as e:
                self.logger.error(
                    "Unable to connect to server '{}' ({}:{}), reason: {}".
                    format(self.serverfolder, self.settings['server'],
                           self.settings['port'], e))
            else:
                #Connecting was successful, authenticate
                if 'password' in self.settings and len(
                        self.settings['password']) > 0:
                    self.sendLineToServer("PASS " + self.settings['password'])
                self.sendLineToServer("NICK {}".format(
                    self.settings['nickname']))
                #Use the specified realname, or fall back to the username if none is provided
                realname = self.settings.get('realname',
                                             self.settings['nickname'])
                self.sendLineToServer(
                    "USER {} 4 * :{}".format(self.settings['nickname'],
                                             realname)
                )  #The '4' means we want WALLOPS messages but not invisibility

                #Start listening for replies
                self.handleConnection()
                #If we reach here, 'handleConnection' returned, so we apparently lost the connection (either accidentally or intentionally)

                #Check if we were still sending messages
                if self.lineSendingGreenlet:
                    # Kill the message sending greenlet to prevent errors
                    self.lineSendingGreenlet.kill()
                    self.lineSendingGreenlet = None
                    #Also clear the message queue, just in case something in there caused the disconnect
                    self.linesToSend = None

                #Clear the channels and users lists
                self.channelsUserList = {}
                self.isUpdatingChannelsUserList = False

                #Shutdown here because it only makes sense if we have been connected previously
                self.ircSocket.shutdown(gevent.socket.SHUT_RDWR)

            # We lost the connection, so close the socket and store that we lost connection
            self.ircSocket.close()
            self.ircSocket = None
            self.connectedAt = None

            #If the connection couldn't be established or got closed, check if we need to re-establish it
            if not self.shouldReconnect:
                self.logger.info(
                    "|{}| Connection closed, shouldn't reconnect, closing down this bot"
                    .format(self.serverfolder))
                break
            #If we reached the maximum reconnection attempts, abort
            if self.reconnectionAttempCount and self.maxConnectionRetries and self.reconnectionAttempCount >= self.maxConnectionRetries:
                self.logger.info(
                    "|{}| Reached max connection retry attempts ({}), closing".
                    format(self.maxConnectionRetries, self.serverfolder))
                break

            if self.reconnectionAttempCount is None:
                self.reconnectionAttempCount = 1
            else:
                self.reconnectionAttempCount += 1
            #Wait increasingly long between reconnection attempts, to give the server a chance to restart
            sleepTime = self.reconnectionAttempCount**3
            self.logger.info(
                "Will try reconnecting to '{}' for attempt {} in {} seconds, max attempts is {}"
                .format(
                    self.serverfolder, self.reconnectionAttempCount, sleepTime,
                    self.maxConnectionRetries
                    if self.maxConnectionRetries else "not set"))
            gevent.sleep(sleepTime)
        #If we ever leave this loop, the bot is shut down. Unregister ourselves
        GlobalStore.bothandler.unregisterBot(self.serverfolder)

    def handleConnection(self):
        #Keep reading for possible incoming messages
        incomingData = ""
        # Set the timeout to 10 minutes, so if our computer loses connection to the server/internet, we notice
        self.ircSocket.settimeout(600)
        while True:
            try:
                incomingData += self.ircSocket.recv(2048)
            except gevent.socket.timeout:
                self.logger.warning(
                    "|{}| Our connection to the server timed out".format(
                        self.serverfolder))
                return
            # A closed connection just makes recv return an empty string. Check for that
            if incomingData == "":
                self.logger.info("|{}| Server closed the connection".format(
                    self.serverfolder))
                return
            # Handle all completely sent messages (delimited by \r\n), leave any unfinished messages for the next loop
            while '\r\n' in incomingData:
                line, incomingData = incomingData.split('\r\n', 1)
                # First deal with the simplest type of message, PING. Just reply PONG
                if line.startswith("PING"):
                    self.sendLineToServer(line.replace("PING", "PONG", 1),
                                          False)
                else:
                    # Let's find out what kind of message this is!
                    lineParts = line.split(" ")
                    # A line consists at least of 'source messageType [target] content'
                    messageSource = lineParts[0]
                    # It usually starts with a colon, remove that
                    if messageSource.startswith(":"):
                        messageSource = messageSource[1:]
                    messageType = lineParts[1]
                    #Convert numerical replies to human-readable ones, if applicable. Otherwise make it uppercase, since that's the standard
                    messageType = Constants.IRC_NUMERIC_TO_NAME.get(
                        messageType, messageType.upper())
                    #The IRC protocol uses ':' to denote the start of a multi-word string. Join those here too, for easier parsing later
                    messageParts = lineParts[2:]
                    for messagePartIndex, messagePart in enumerate(
                            messageParts):
                        if messagePart.startswith(':'):
                            #Join all the separate parts of the wordgroup, and remove the starting colon
                            wordgroup = " ".join(
                                messageParts[messagePartIndex:])[1:]
                            messageParts[messagePartIndex] = wordgroup
                            messageParts = messageParts[:messagePartIndex + 1]
                            break
                    #Check if we have a function to deal with this type of message
                    messageTypeFunction = getattr(self, "irc_" + messageType,
                                                  None)
                    if messageTypeFunction:
                        messageTypeFunction(messageSource, messageParts)
                    else:
                        #No function for this type of message, fall back to a generic function
                        self.irc_unknown_message_type(messageSource,
                                                      messageType, lineParts)

    def irc_RPL_WELCOME(self, source, parameters):
        """Called when we finished connecting to the server"""
        self.logger.info("|{}| Successfully connected".format(
            self.serverfolder))
        # We successfully connected, reset the reconnection count
        self.reconnectionAttempCount = None
        self.connectedAt = time.time()
        # Get the nickname we got assigned from the message
        self.nickname = parameters[0]
        if self.nickname != self.settings['nickname']:
            self.logger.info(
                "|{} Nickname not available. Wanted '{}', got '{}'".format(
                    self.serverfolder, self.settings['nickname'],
                    self.nickname))
        # Inform all the modules that we connected
        message = IrcMessage("RPL_WELCOME", self, None, source,
                             " ".join(parameters))
        GlobalStore.commandhandler.handleMessage(message)
        # Join the channels we should, if there are any
        if len(self.settings['joinChannels']) == 0:
            self.logger.info("|{}| No join channels specified, idling".format(
                self.serverfolder))
        else:
            for channel in self.settings['joinChannels']:
                self.joinChannel(channel)

    def joinChannel(self, channelname):
        if channelname[0] not in Constants.CHANNEL_PREFIXES:
            channelname = "#" + channelname
        if channelname in self.channelsUserList:
            self.logger.warning(
                "|{}| Asked to join '{}' but I'm already there".format(
                    self.serverfolder, channelname))
        else:
            self.sendLineToServer("JOIN {}".format(channelname))

    def leaveChannel(self, channelName, leaveMessage="Leaving..."):
        if channelName not in self.channelsUserList:
            self.logger.warning(
                "|{}| Asked to leave '{}', but I'm not there".format(
                    self.serverfolder, channelName))
        else:
            self.sendLineToServer("PART {} :{}".format(channelName,
                                                       leaveMessage))

    def setNick(self, nickname):
        self.sendLineToServer("NICK " + nickname)

    def irc_ERR_NICKNAMEINUSE(self, source, parameters):
        # The nickname we want is apparently in use. Just append an underscore and try again
        newNicknameAttempt = parameters[1] + "_"
        self.logger.info(
            "|{}| Requested nickname '{}' in use, retrying with nickname '{}'".
            format(self.serverfolder, parameters[1], newNicknameAttempt))
        self.nickname = newNicknameAttempt
        self.sendLineToServer("NICK " + newNicknameAttempt)

    #Create a list of user addresses per channel
    def retrieveChannelUsers(self, channel):
        self.isUpdatingChannelsUserList = True
        #Make sure we don't get duplicate data
        if channel in self.channelsUserList:
            self.channelsUserList.pop(channel)
        self.sendLineToServer("WHO {}".format(channel))

    def quit(self, quitMessage=None):
        self.shouldReconnect = False
        #If we are connected to a server, let it know we want to quit
        if self.connectedAt is not None:
            if quitMessage:
                self.sendLineToServer("QUIT :" + quitMessage)
            else:
                self.sendLineToServer("QUIT")
        # If we're not connected (yet?), stop trying to connect
        elif self.connectionManagerGreenlet:
            self.logger.info(
                "|{}| Asked to quit, but not connected. Stopping wait before next connection attempt"
                .format(self.serverfolder))
            self.connectionManagerGreenlet.kill()
            GlobalStore.bothandler.unregisterBot(self.serverfolder)
        else:
            #I don't know how we'd end up without a connection greenlet, but best handle it anyway
            self.logger.info(
                "|{}| Asked to quit and don't have a connection greenlet. Unregistering"
                .format(self.serverfolder))
            GlobalStore.bothandler.unregisterBot(self.serverfolder)

    #MESSAGE TYPE HANDLING FUNCTIONS
    def irc_unknown_message_type(self, source, messageType, messageParts):
        self.logger.info(
            "|{}| Received unknown message type '{}' from {}: {}".format(
                self.serverfolder, messageType, source,
                " ".join(messageParts)))

    def ctcp_unknown_message_type(self, ctcpType, user, messageTarget,
                                  message):
        self.logger.info(
            "|{}| Received unknown CTCP command '{}' on {} from {}, message '{}'"
            .format(self.serverfolder, ctcpType, messageTarget, user, message))

    def irc_RPL_MOTD(self, prefix, params):
        self.messageLogger.log("Server message of the day: " + params[1])

    def irc_JOIN(self, prefix, params):
        """Called when a user or the bot joins a channel"""
        # 'prefix' is the user, 'params' is a list with apparently just one entry, the channel
        message = IrcMessage('join', self, prefix, params[0])
        self.messageLogger.log(
            "JOIN: {nick} ({address})".format(nick=message.userNickname,
                                              address=prefix), params[0])
        # If we just joined a channel, or if don't have a record of this channel yet, get all the users in it
        if message.userNickname == self.nickname or params[
                0] not in self.channelsUserList:
            self.retrieveChannelUsers(params[0])
        # If we don't know this user yet, add it to our list
        elif prefix not in self.channelsUserList[params[0]]:
            self.channelsUserList[params[0]].append(prefix)
        GlobalStore.commandhandler.handleMessage(message)

    def irc_PART(self, prefix, params):
        """Called when a user or the bot leaves a channel"""
        # 'prefix' is the user, 'params' is a list with only the channel
        message = IrcMessage('part', self, prefix, params[0])
        self.messageLogger.log(
            "PART: {nick} ({address})".format(nick=message.userNickname,
                                              address=prefix), params[0])
        # If a user parts before we have a proper channellist built, catch that error
        if params[0] not in self.channelsUserList:
            self.logger.warning(
                "|{}| Unexpected PART, user '{}' parted from channel '{}' but we had no record of them"
                .format(self.serverfolder, prefix, params[0]))
            # Schedule a rebuild of the userlist
            if not self.isUpdatingChannelsUserList:
                self.retrieveChannelUsers(params[0])
        # Keep track of the channels we're in
        elif message.userNickname == self.nickname:
            self.channelsUserList.pop(params[0])
        # Keep track of channel users
        elif prefix in self.channelsUserList[params[0]]:
            self.channelsUserList[params[0]].remove(prefix)
        GlobalStore.commandhandler.handleMessage(message)

    def irc_QUIT(self, prefix, params):
        """Called when a user quits"""
        # 'prefix' is the user address, 'params' is a single-item list with the quit messages
        # log for every channel the user was in that they quit
        message = IrcMessage('quit', self, prefix, None, params[0])
        logMessage = "QUIT: {nick} ({address}): '{quitmessage}' ".format(
            nick=message.userNickname, address=prefix, quitmessage=params[0])
        for channel, userlist in self.channelsUserList.iteritems():
            if prefix in userlist:
                self.messageLogger.log(logMessage, channel)
                userlist.remove(prefix)
        GlobalStore.commandhandler.handleMessage(message)

    def irc_KICK(self, prefix, params):
        """Called when a user is kicked"""
        # 'prefix' is the kicker, params[0] is the channel, params[1] is the user address of the kicked, params[-1] is the kick reason
        message = IrcMessage('kick', self, prefix, params[0], params[-1])
        kickedUserNick = params[1].split("!", 1)[0]
        self.messageLogger.log(
            "KICK: {} was kicked by {}, reason: '{}'".format(
                kickedUserNick, message.userNickname, params[-1]), params[0])
        # Keep track of the channels we're in
        if kickedUserNick == self.nickname:
            if params[0] in self.channelsUserList:
                self.channelsUserList.pop(params[0])
        elif params[1] in self.channelsUserList[params[0]]:
            self.channelsUserList[params[0]].remove(params[1])
        GlobalStore.commandhandler.handleMessage(message)

    def irc_NICK(self, prefix, params):
        """Called when a user or me change their nickname"""
        # 'prefix' is the full user address with the old nickname, params[0] is the new nickname
        message = IrcMessage('nickchange', self, prefix, None, params[0])
        oldnick = message.userNickname
        newnick = params[0]
        # New nick plus old address
        newaddress = newnick + "!" + prefix.split("!", 1)[1]
        # If it's about us, apparently a nick change was successful
        if oldnick == self.nickname:
            self.nickname = newnick
            self.logger.info(
                "|{}| Our nick got changed from '{}' to '{}'".format(
                    self.serverfolder, oldnick, self.nickname))
        # Log the change in every channel where it's relevant
        for channel, userlist in self.channelsUserList.iteritems():
            if prefix in userlist:
                # Update the userlists for all channels this user is in
                userlist.append(newaddress)
                userlist.remove(prefix)
                self.messageLogger.log(
                    "NICK CHANGE: {oldnick} changed their nick to {newnick}".
                    format(oldnick=oldnick, newnick=newnick), channel)
        GlobalStore.commandhandler.handleMessage(message)

    def irc_MODE(self, prefix, params):
        #There are two possible MODE commands
        # The first is server-wide. Here the prefix is our user address, the first param is our username, and the second param is the mode change
        if len(params) == 2:
            self.logger.info("|{}| Our mode got set to '{}'".format(
                self.serverfolder, params[1]))
        # The second is channel-specific. Here the prefix is who or what is making the change, the first param is the channel,
        #  the second param is the mode change, and the rest of the params are the nicks whose mode got changed
        else:
            #It's not always a user making the change, check if it's a user address
            modeChanger = prefix
            if '!' in modeChanger:
                modeChanger = modeChanger.split('!', 1)[0]
            self.messageLogger.log(
                "{} set mode to '{}' of user(s) {}".format(
                    modeChanger, params[1], ", ".join(params[2:])), params[0])

    def irc_TOPIC(self, prefix, params):
        self.logger.debug(
            "irc_TOPIC called, prefix is '{}', params is '{}'".format(
                prefix, params))

    def irc_RPL_TOPIC(self, prefix, params):
        self.logger.debug(
            "irc_RPL_TOPIC called, prefix is '{}', params is '{}'".format(
                prefix, params))

    def irc_RPL_NOTOPIC(self, prefix, params):
        self.logger.debug(
            "irc_RPL_NOTOPIC called, prefix is '{}', params is '{}'".format(
                prefix, params))

    def irc_RPL_WHOREPLY(self, prefix, params):
        #'prefix' is the server, 'params' is a list, with meaning [own_nick, channel, other_username, other_address, other_server, other_nick, flags, hops realname]
        # Flags can be H for active or G for away, and a * for oper, + for voiced
        if params[1] not in self.channelsUserList:
            self.channelsUserList[params[1]] = []
        self.channelsUserList[params[1]].append(
            "{nick}!{username}@{address}".format(nick=params[5],
                                                 username=params[2],
                                                 address=params[3]))

    def irc_RPL_ENDOFWHO(self, prefix, params):
        self.isUpdatingChannelsUserList = False
        self.logger.info("|{}| Userlist for channels {} collected".format(
            self.serverfolder, ", ".join(self.channelsUserList.keys())))

    #CTCP FUNCTIONS
    def ctcp_ACTION(self, user, messageSource, messageText):
        self.handleMessage(user, messageSource, messageText, 'action')

    def ctcp_PING(self, user, messageSource, messageText):
        self.messageLogger.log("Received PING request from '{}'".format(user),
                               messageSource)
        usernick = user.split('!', 1)[0]
        self.sendMessage(
            usernick,
            "PING " + messageText if messageText else str(time.time()),
            'notice')

    def ctcp_SOURCE(self, user, messageSource, messageText):
        self.messageLogger.log(
            "Received SOURCE request from '{}'".format(user), messageSource)
        usernick = user.split('!', 1)[0]
        self.sendMessage(
            usernick,
            "Thanks for the interest! You can read through my innards at https://github.com/Didero/DideRobot",
            'notice')

    def ctcp_TIME(self, user, messageSource, messageText):
        self.messageLogger.log("Received TIME request from '{}'".format(user),
                               messageSource)
        usernick = user.split('!', 1)[0]
        self.sendMessage(usernick, time.strftime("%a %d-%m-%Y %H:%M:%S %Z"),
                         'notice')

    def ctcp_VERSION(self, user, messageSource, messageText):
        self.messageLogger.log(
            "Received VERSION request from '{}'".format(user), messageSource)
        usernick = user.split('!', 1)[0]
        self.sendMessage(
            usernick,
            "I don't have a set version, since I'm updated pretty frequently. I appreciate the interest though!",
            'notice')

    #HUMAN COMMUNICATION FUNCTIONS
    def irc_PRIVMSG(self, user, messageParts):
        # First part of the messageParts is the channel the message came in from, or the user if it was a PM
        # Second part is the actual message
        messageSource = messageParts[0]
        # If the actual message (past the first colon) starts with 'chr(1)', it means it's a special CTCP message (like an action)
        if len(messageParts[1]
               ) > 0 and messageParts[1][0] == Constants.CTCP_DELIMITER:
            #First section is the CTCP type
            # Make the type uppercase, since that's the standard (also means 'unknown_message_type' can't accidentally be called)
            ctcpType = messageParts[1].upper()
            messageText = None
            #Sometimes a message is appended (like for an ACTION), check for that
            if " " in ctcpType:
                ctcpType, messageText = messageParts[1].split(" ", 1)
                #The message could also end with a 'chr(1)', remove that
                if messageText.endswith(Constants.CTCP_DELIMITER):
                    messageText = messageText[:-1]
            #The CTCP type could end with the CTCP delimiter
            if ctcpType.endswith(Constants.CTCP_DELIMITER):
                ctcpType = ctcpType[:-1]
            ctcpType = ctcpType[1:]  #Remove the CTCP delimiter
            #Check if we have a function to handle this type of CTCP message, otherwise fall back on a default
            ctcpFunction = getattr(self, "ctcp_" + ctcpType, None)
            if ctcpFunction:
                ctcpFunction(user, messageSource, messageText)
            else:
                self.ctcp_unknown_message_type(ctcpType, user, messageSource,
                                               messageText)
        #Normal message
        else:
            self.handleMessage(user, messageSource, messageParts[1], "say")

    def irc_NOTICE(self, user, messageParts):
        self.handleMessage(user, messageParts[0], messageParts[1], 'notice')

    def handleMessage(self, user, channel, messageText, messageType="say"):
        """Called when the bot receives a message, which can be either in a channel or in a private message, as text or an action."""
        usernick = user.split("!", 1)[0]
        logsource = channel
        if channel == self.nickname or channel == '*':  #If a server wants to send a message to you before it knows your nick, it uses *
            logsource = usernick
        logtext = "({source}) "
        if messageType == 'say':
            logtext = "{user}: {message}"
        elif messageType == 'action':
            logtext = "*{user} {message}"
        elif messageType == 'notice':
            logtext = "[notice] {user}: {message}"

        self.messageLogger.log(
            logtext.format(user=usernick, message=messageText), logsource)
        message = IrcMessage(messageType, self, user, channel, messageText)
        #Let the CommandHandler see if a module needs to do something with this message
        GlobalStore.commandhandler.handleMessage(message)

    #SENDING OUT MESSAGES
    def sendLineToServer(self, lineToSend, shouldLogMessage=True):
        if not self.ircSocket:
            self.logger.error(
                "|{}| Asked to send line '{}' to server, but socket closed".
                format(self.serverfolder, lineToSend))
            return
        if shouldLogMessage:
            self.logger.debug("|{}| > {}".format(self.serverfolder,
                                                 lineToSend))
        self.ircSocket.send(lineToSend + "\r\n")

    def irc_ERR_NOTEXTTOSEND(self, prefix, params):
        self.logger.error(
            "|{}| We just sent an empty line to the server, which is probably a bug in a module!"
            .format(self.serverfolder))

    @staticmethod
    def formatCtcpMessage(ctcpType, messageText):
        return "{delim}{ctcpType} {msg}{delim}".format(
            delim=Constants.CTCP_DELIMITER, ctcpType=ctcpType, msg=messageText)

    def sendLineFromQueue(self):
        try:
            while True:
                #Verify that we're still connected and there is still a message in the queue, otherwise reset everything
                if self.connectedAt is None or not self.linesToSend or len(
                        self.linesToSend) == 0:
                    self.linesToSend = None
                    self.lineSendingGreenlet = None
                    break
                #Remove the first queued message and send it
                self.sendLineToServer(self.linesToSend.pop(0))
                #Keep going through the message queue until it's empty
                gevent.sleep(self.secondsBetweenLineSends)
        except gevent.GreenletExit:
            self.logger.info("|{}| Line sender greenlet was killed".format(
                self.serverfolder))

    def queueLineToSend(self, lineToSend):
        #If there's no rate limiting, there's no need for queueing either. Send the message now
        if not self.secondsBetweenLineSends:
            self.sendLineToServer(lineToSend)
            return
        #If there are no lines queued, the list is set to 'None'. Create a new list
        if not self.linesToSend:
            self.linesToSend = [lineToSend]
        # Add the message to the queue
        else:
            self.linesToSend.append(lineToSend)
        # If there's not yet a greenlet clearing the message queue, create one
        if not self.lineSendingGreenlet:
            self.lineSendingGreenlet = gevent.spawn(self.sendLineFromQueue)

    def sendMessage(self, target, messageText, messageType='say'):
        #Only say something if we're not muted, or if it's a private message or a notice
        if not self.isMuted or target[
                0] not in Constants.CHANNEL_PREFIXES or messageType == 'notice':
            #Make sure we're not trying to send Unicode
            if isinstance(messageText, unicode):
                try:
                    messageText = messageText.encode(encoding='utf-8',
                                                     errors='replace')
                except (UnicodeDecodeError, UnicodeEncodeError):
                    self.logger.warning(
                        "|{}| [sendMessage] Error encoding message to string (is now type '{}'): '{}'"
                        .format(self.serverfolder, type(messageText),
                                messageText))
            #It can't handle unicode message targets either
            if isinstance(target, unicode):
                target = target.encode('utf-8')
            logtext = ""
            messageCommand = "PRIVMSG"
            if messageType == 'action':
                #An action is just a special type of Say
                logtext += "*"
                messageText = self.formatCtcpMessage("ACTION", messageText)
            elif messageType == 'notice':
                logtext += "[notice] "
                messageCommand = "NOTICE"
            logtext += "{user}: {message}"
            line = "{} {} :{}".format(messageCommand, target, messageText)
            extraLines = None
            #Turn newlines in this line into multiple lines
            if '\n' in line or '\r' in line:
                extraLines = line.splitlines()
                line = extraLines.pop(0)
            #Check if the line isn't too long to send
            if len(line) >= Constants.MAX_LINE_LENGTH:
                if not extraLines:
                    extraLines = [line[Constants.MAX_LINE_LENGTH:]]
                else:
                    extraLines.insert(0, line[Constants.MAX_LINE_LENGTH:])
                line = line[:Constants.MAX_LINE_LENGTH]
            if target[0] not in Constants.CHANNEL_PREFIXES:
                #If it's a PM, bypass the message queue
                self.sendLineToServer(line)
            else:
                self.queueLineToSend(line)
            self.messageLogger.log(
                logtext.format(user=self.nickname, message=messageText),
                target)
            #Make sure any extra lines get sent too
            if extraLines:
                for extraLine in extraLines:
                    self.sendMessage(target, extraLine, messageType)

    #USER LIST CHECKING FUNCTIONS
    def isUserAdmin(self, user, userNick=None, userAddress=None):
        return self.isUserInList(self.settings['admins'], user, userNick,
                                 userAddress)

    def shouldUserBeIgnored(self, user, userNick=None, userAddress=None):
        return self.isUserInList(self.settings['userIgnoreList'], user,
                                 userNick, userAddress)

    @staticmethod
    def isUserInList(userlist, user, userNick=None, userAddress=None):
        if user is None:
            return False
        if user in userlist or user.lower() in userlist:
            return True
        #If a usernick is provided, use that, otherwise split the full user address ourselves (if possible)
        if '!' not in user:
            return False
        if userNick is None or userAddress is None:
            userNick, userAddress = user.split('!', 1)
        if userNick in userlist or userNick.lower(
        ) in userlist or userAddress in userlist or userAddress.lower(
        ) in userlist:
            return True
        return False
 def __init__(self):
     self.googlePhotos = self.getPhotosService()
     self.logger = MessageLogger(logName="download.log")
class Downloader(object):
    def __init__(self):
        self.googlePhotos = self.getPhotosService()
        self.logger = MessageLogger(logName="download.log")

    def getPhotosService(self):
        creds = None
        # The file token.pickle stores the user's access and refresh tokens, and is
        # created automatically when the authorization flow completes for the first
        # time.
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # If there are no (valid) credentials available, let the user log in.
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    'credentials.json', SCOPES)
                creds = flow.run_local_server(port=0)
            # Save the credentials for the next run
            with open('token.pickle', 'wb') as token:
                pickle.dump(creds, token)

        service = build('photoslibrary', 'v1', credentials=creds)
        return service

    def listAllAlbums(self, pageSize=50):
        albumsList = []
        nextPageToken = 'Dummy'
        while nextPageToken != '':
            nextPageToken = '' if nextPageToken == 'Dummy' else nextPageToken
            results = self.googlePhotos.albums().list(
                pageSize=pageSize, pageToken=nextPageToken).execute()
            albums = results.get('albums', [])
            nextPageToken = results.get('nextPageToken', '')
            for album in albums:
                albumsList.append(album)
        return albumsList

    def getAlbum(self, albumId):
        return self.googlePhotos.albums().get(albumId=albumId).execute()

    def listAlbumPhotos(self, albumId, pageSize=50):
        photoList = []
        nextPageToken = 'Dummy'
        while nextPageToken != '':
            nextPageToken = '' if nextPageToken == 'Dummy' else nextPageToken
            results = self.googlePhotos.mediaItems().search(
                body={
                    "albumId": albumId,
                    "pageSize": pageSize,
                    "pageToken": nextPageToken
                }).execute()
            photos = results.get('mediaItems', [])
            nextPageToken = results.get('nextPageToken', '')
            for photo in photos:
                photoList.append(photo)
            return photoList

    def getPhoto(self, photoId):
        return self.googlePhotos.mediaItems().get(
            mediaItemId=photoId).execute()

    def getPhotoBinary(self, photo):
        return self.getPhotoBinaryFromBaseUrl(photo['baseUrl'])

    def getPhotoBinaryFromBaseUrl(self, baseUrl):
        baseUrl = baseUrl + "=d"
        res = request.urlopen(baseUrl)
        return res.read()

    def saveToFile(self, data, albumName, fileName, coverPhoto=False):
        if coverPhoto:
            filePath = topLevelDir + "\\" + albumName + "\\" + "Cover Photo\\" + fileName
        else:
            filePath = topLevelDir + "\\" + albumName + "\\" + fileName
        with open(filePath, "wb") as _file:
            _file.write(data)

    def makeAlbumDir(self, albumName):
        newDirName = topLevelDir + '\\' + albumName
        os.mkdir(newDirName)

    def makeCoverPhotoDir(self, albumName):
        newDirName = topLevelDir + '\\' + albumName + '\\' + "Cover Photo"
        os.mkdir(newDirName)

    def makeTagsDir(self, albumName):
        newDirName = topLevelDir + '\\' + albumName + '\\' + "Tags"
        os.mkdir(newDirName)

    def downloadAlbum(self, albumId):
        album = self.getAlbum(albumId)
        albumName = album['title']
        msg = "\tDownloading " + albumName + "..."
        self.logger.log(msg)

        coverPhotoBaseUrl = album['coverPhotoBaseUrl']
        coverPhotoMediaItemId = album['coverPhotoMediaItemId']

        self.makeAlbumDir(albumName)
        self.makeCoverPhotoDir(albumName)
        self.makeTagsDir(albumName)

        tags = self.getAlbumTags(albumName)
        self.writeTagsToFile(albumName, tags)

        coverPhoto = self.getPhoto(coverPhotoMediaItemId)
        data = self.getPhotoBinary(coverPhoto)
        self.saveToFile(data,
                        albumName,
                        coverPhoto['filename'],
                        coverPhoto=True)

        photos = self.listAlbumPhotos(albumId)

        for photo in photos:
            if photo['id'] != coverPhoto['id']:
                fileName = photo['filename']
                data = self.getPhotoBinary(photo)
                self.saveToFile(data, albumName, fileName)

        return albumName

    def getAlbumTags(self, albumName):
        tags = []
        with open("tags.pickle", "rb") as _file:
            tagCandidates = pickle.load(_file)
            for key in tagCandidates:
                if tagCandidates[key].lower() in albumName.lower():
                    if tagCandidates[key] not in tags:
                        tags.append(tagCandidates[key])
        return tags

    def writeTagsToFile(self, albumName, tags):
        tagsDir = topLevelDir + '\\' + albumName + '\\' + "Tags\\"
        tagsFile = tagsDir + "tags.txt"
        with open(tagsFile, "w") as _file:
            for tag in tags:
                _file.write(tag + "\n")

    def getAlbums(self):
        with open("albumList.txt") as _file:
            albums = _file.readlines()
            albums = [album.rstrip('\n') for album in albums]
            albums = [album.split(' :: ') for album in albums]
            return albums

    def downloadAlbums(self):

        numAlbums = len(self.getAlbums())
        failCount = 0
        failList = []

        msg = "Downloading " + str(numAlbums) + " albums..."
        self.logger.log(msg)

        for album in self.getAlbums():
            albumName = album[0]
            albumId = album[1]
            try:
                self.downloadAlbum(albumId)
                msg = "\tSuccess downloading " + albumName + "\n"
                self.logger.log(msg)
            except Exception as e:
                msg = "\t" + str(e) + "\n"
                self.logger.log(msg)
                failCount = failCount + 1
                failList.append(albumName)

        if failCount > 0:
            msg = "Failed to download " + str(failCount) + " albums"
            self.logger.log(msg)
            for name in failList:
                self.logger.log(name)
        else:
            msg = "All albums downloaded successfully"
            self.logger.log(msg)

        self.logger.endLog()
Beispiel #7
0
 def connectionMade(self):
     irc.IRCClient.connectionMade(self)
     self.setNick(self.factory.nickname)
     self.logger = MessageLogger(open(self.factory.filename, "a"))
     self.logger.log("[connected at %s]" % 
                     time.asctime(time.localtime(time.time())))
class LogBot(irc.IRCClient):
    """A logging IRC bot."""
    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())))

    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."""
        self.join(self.factory.channel)

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

    def kickedFrom(self, channel, kicker, message):
        """This function is called, when bot is kicked out of the channel"""
        """Bot logs information and reconnects to the channel"""
        self.logger.log("[%s kicked me from %s - reason %s]" % (kicker, channel, message))
        self.join(self.factory.channel)

    def topicUpdated(self, user, channel, newTopic):
        """This function logs new topic to log"""
        self.logger.log("[%s changed topic on %s to %s]" % (user,channel,newTopic))

    def userKicked(self, kickee, channel, kicker, message):
        """When one of the users get kicked..."""
        self.logger.log("[%s has been kicked from %s by %s with message: %s]" % (kickee,channel,kicker,message))

    def privmsg(self, user, channel, msg):
        """This will get called when the bot receives a message."""
        user = user.split('!', 1)[0]
        self.logger.log("<%s> %s" % (user, msg))

        # Check to see if they're sending me a private message
        if channel == self.factory.nick:
            msg = "It isn't nice to whisper!  Play nice with the group."
            self.msg(user, msg)
            return

        # Otherwise check to see if it is a message directed at me
        if msg.startswith(self.factory.nick + ":") or self.factory.nick in msg:
            msg = "%s: I am a log bot by https://github.com/thelehhman" % user
            self.msg(channel, msg)
            self.logger.log("<%s> %s" % (self.factory.nick, msg))

    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))


    # For fun, override the method that determines how a nickname is changed on
    # collisions. The default method appends an underscore.
    def alterCollidedNick(self, nickname):
        """
        Generate an altered version of a nickname that caused a collision in an
        effort to create an unused related name for subsequent registration.
        """
        nickname = nickname+"_"
        return nickname
Beispiel #9
0
 def __init__(self, api_key, api_secret):
     self.flickr = flickrapi.FlickrAPI(api_key, api_secret)
     self.authenticateFlickr()
     self.api_key = api_key
     self.logger = MessageLogger(logName="upload.log")
Beispiel #10
0
class Uploader(object):
    def __init__(self, api_key, api_secret):
        self.flickr = flickrapi.FlickrAPI(api_key, api_secret)
        self.authenticateFlickr()
        self.api_key = api_key
        self.logger = MessageLogger(logName="upload.log")

    def authenticateFlickr(self):
        if not self.flickr.token_valid(perms='write'):

            # Get a request token
            self.flickr.get_request_token(oauth_callback='oob')

            # Open a browser at the authentication URL. Do this however
            # you want, as long as the user visits that URL.
            authorize_url = self.flickr.auth_url(perms='write')
            webbrowser.open_new_tab(authorize_url)

            # Get the verifier code from the user. Do this however you
            # want, as long as the user gives the application the code.
            verifier = str(input('Verifier code: '))

            # Trade the request token for an access token
            self.flickr.get_access_token(verifier)

    def uploadPhoto(self, albumName, photoName, coverPhoto=False):
        if coverPhoto:
            filePath = topLevelDir + "\\" + albumName + "\\Cover Photo\\" + photoName
        else:
            filePath = topLevelDir + "\\" + albumName + "\\" + photoName

        return self.flickr.upload(filePath,
                                  FileWithCallback(filePath, callback))

    # Add a try block in this function to handle the exception
    # of trying to use an invalid filename
    def uploadAlbum(self, albumName):

        msg = "\tUploading " + albumName + "..."
        self.logger.log(msg)

        albumPath = topLevelDir + "\\" + albumName
        coverPath = albumPath + "\\Cover Photo"
        tagsPath = albumPath + "\\Tags\\tags.txt"

        with open(tagsPath, "r") as _file:
            tags = _file.readlines()
            tags = [tag.strip() for tag in tags]
            tags = " ".join(tags)

        _dir = os.listdir(coverPath)

        if len(_dir) == 0:
            raise Exception("The \"Cover Photo\" directory is empty")

        if len(_dir) > 1:
            raise Exception(
                "There can only be one photo in the \"Cover Photo\" directory")

        fileName = _dir[0]
        res = self.uploadPhoto(albumName, fileName, coverPhoto=True)
        if res is None:
            raise Exception("Failed to upload cover photo")

        coverPhotoId = self.getPhotoId(res)
        self.setPhotoTags(coverPhotoId, tags)
        res = self.createAlbum(albumName, coverPhotoId)
        if res is None:
            raise Exception("Failed to create album on Flickr")

        albumId = self.getPhotosetId(res)

        _dir = os.listdir(albumPath)
        for _file in _dir:
            if _file != "Cover Photo" and _file != "Tags":
                res = self.uploadPhoto(albumName, _file)
                photoId = self.getPhotoId(res)
                self.setPhotoTags(photoId, tags)
                self.addToAlbum(albumId, photoId)

    def createAlbum(self, albumName, primary_photo_id):
        return self.flickr._flickr_call(method='flickr.photosets.create',
                                        api_key=self.api_key,
                                        title=albumName,
                                        primary_photo_id=primary_photo_id)

    def addToAlbum(self, albumId, photoId):
        return self.flickr._flickr_call(method='flickr.photosets.addPhoto',
                                        api_key=self.api_key,
                                        photoset_id=albumId,
                                        photo_id=photoId)

    def setPhotoTags(self, photoId, tags):
        return self.flickr._flickr_call(method='flickr.photos.setTags',
                                        api_key=self.api_key,
                                        photo_id=photoId,
                                        tags=tags)

    def getPhotoId(self, res):
        return res[0].text

    def getPhotosetId(self, res):
        et = ET.fromstring(res.decode("utf-8"))
        return et[0].attrib['id']

    def getAlbums(self):
        with open("albumList.txt") as _file:
            albums = _file.readlines()
            albums = [album.rstrip('\n') for album in albums]
            albums = [album.split(' :: ')[0] for album in albums]
            return albums

    def uploadAlbums(self):

        numAlbums = len(self.getAlbums())
        failCount = 0
        failList = []

        msg = "Uploading " + str(numAlbums) + " albums..."
        self.logger.log(msg)

        for albumName in self.getAlbums():
            try:
                self.uploadAlbum(albumName)
                msg = "\tSuccess uploading " + albumName + "\n"
                self.logger.log(msg)
            except Exception as e:
                msg = "\t" + str(e) + "\n"
                self.logger.log(msg)
                failCount = failCount + 1
                failList.append(albumName)

        if failCount > 0:
            msg = "Failed to upload " + str(failCount) + " albums"
            self.logger.log(msg)
            for name in failList:
                self.logger.log(name)
        else:
            msg = "All albums uploaded successfully"
            self.logger.log(msg)

        self.logger.endLog()
Beispiel #11
0
class DideRobot(object):
	def __init__(self, serverfolder):
		self.logger = logging.getLogger('DideRobot')
		self.logger.info("New bot for server '{}' created".format(serverfolder))

		#Initialize some variables (in init() instead of outside it to prevent object sharing between instances)
		self.serverfolder = serverfolder
		self.ircSocket = None
		self.nickname = None  # Will get set once we connect, when we know if we have the nickname we want
		self.channelsUserList = {}  # Will be a dict with joined channels as keys and a list of users in those channels as values
		self.isUpdatingChannelsUserList = False
		self.isMuted = False

		self.connectedAt = None  # Will be set to the timestamp on which we connect. 'None' means we're not connected
		self.connectionManagerGreenlet = None  # This will get a reference to the greenlet keeping the connection alive. If this ends, the bot is closed down
		self.shouldReconnect = True
		self.reconnectionAttempCount = None  # Will keep a count of how many times we've tried to connect, to see if we've exceeded the limit (if any)
		self.maxConnectionRetries = None  # None means unlimited attempts, can be set by settings file

		self.secondsBetweenLineSends = None  # If it's 'None', there's no rate limiting, otherwise it's a float of seconds between line sends
		self.linesToSend = None  # A list with all the lines to send if there is a queue and rate limiting
		self.lineSendingGreenlet = None  # Will store the greenlet that is currently working its way through the message queue, or is None if there is none

		self.commandPrefix = ""  # Pulled from the settings file, separate variable because it's referenced a lot
		self.commandPrefixLength = 0  # The length if the prefix is also often needed, prevent constant recalculation

		#Load the settings, and only connect to the server if that succeeded
		self.settings = BotSettingsManager(self.serverfolder)
		if self.settings.loadedSuccessfully:
			self.parseSettings()
			self.messageLogger = MessageLogger(self)
			self.connectionManagerGreenlet = gevent.spawn(self.keepServerConnectionAlive)
		else:
			self.logger.error("|{}| Invalid settings file, shutting down".format(self.serverfolder))
			#Also tell the bot manager that we'll stop existing
			GlobalStore.bothandler.unregisterBot(self.serverfolder)

	def parseSettings(self):
		"""Retrieve some frequently-used values, to store them in a more easily-accessible way"""
		#The command prefix is going to be needed often, as will its length. Put that in an easy-to-reach place
		self.commandPrefix = self.settings['commandPrefix']
		self.commandPrefixLength = len(self.commandPrefix)

		#Load in the maximum connection settings to try, if there is any
		self.maxConnectionRetries = self.settings.get('maxConnectionRetries', -1)
		#Assume values smaller than zero mean endless retries
		if self.maxConnectionRetries < 0:
			self.maxConnectionRetries = None

		#Time in seconds (can have decimals) between line sends, because some servers are rate limited
		self.secondsBetweenLineSends = self.settings.get('minSecondsBetweenMessages', -1)
		if self.secondsBetweenLineSends <= 0:
			self.secondsBetweenLineSends = None

	def reloadSettings(self):
		self.settings.reloadSettings(True)
		if self.settings.loadedSuccessfully:
			self.parseSettings()
			return True
		else:
			return False

	#CONNECTION FUNCTIONS
	def keepServerConnectionAlive(self):
		while True:
			# Open a connection
			self.ircSocket = gevent.socket.socket(gevent.socket.AF_INET, gevent.socket.SOCK_STREAM)
			self.ircSocket.settimeout(20.0)  #Set the timout to establishing a connection, gets increased when we successfully connect
			self.logger.info("Connecting to {} ({} on port {})".format(self.serverfolder, self.settings['server'], self.settings['port']))
			try:
				self.ircSocket.connect((self.settings['server'], self.settings['port']))
			except (gevent.socket.timeout, gevent.socket.error, gevent.socket.herror, gevent.socket.gaierror) as e:
				self.logger.error("Unable to connect to server '{}' ({}:{}), reason: {}".format(self.serverfolder, self.settings['server'], self.settings['port'], e))
			else:
				#Connecting was successful, authenticate
				if 'password' in self.settings and len(self.settings['password']) > 0:
					self.sendLineToServer("PASS " + self.settings['password'])
				self.sendLineToServer("NICK {}".format(self.settings['nickname']))
				#Use the specified realname, or fall back to the username if none is provided
				realname = self.settings.get('realname', self.settings['nickname'])
				self.sendLineToServer("USER {} 4 * :{}".format(self.settings['nickname'], realname))  #The '4' means we want WALLOPS messages but not invisibility

				#Start listening for replies
				self.handleConnection()
				#If we reach here, 'handleConnection' returned, so we apparently lost the connection (either accidentally or intentionally)

				#Check if we were still sending messages
				if self.lineSendingGreenlet:
					# Kill the message sending greenlet to prevent errors
					self.lineSendingGreenlet.kill()
					self.lineSendingGreenlet = None
					#Also clear the message queue, just in case something in there caused the disconnect
					self.linesToSend = None

				#Clear the channels and users lists
				self.channelsUserList = {}
				self.isUpdatingChannelsUserList = False

				#Shutdown here because it only makes sense if we have been connected previously
				self.ircSocket.shutdown(gevent.socket.SHUT_RDWR)

			# We lost the connection, so close the socket and store that we lost connection
			self.ircSocket.close()
			self.ircSocket = None
			self.connectedAt = None

			#If the connection couldn't be established or got closed, check if we need to re-establish it
			if not self.shouldReconnect:
				self.logger.info("|{}| Connection closed, shouldn't reconnect, closing down this bot".format(self.serverfolder))
				break
			#If we reached the maximum reconnection attempts, abort
			if self.reconnectionAttempCount and self.maxConnectionRetries and self.reconnectionAttempCount >= self.maxConnectionRetries:
				self.logger.info("|{}| Reached max connection retry attempts ({}), closing".format(self.maxConnectionRetries, self.serverfolder))
				break

			if self.reconnectionAttempCount is None:
				self.reconnectionAttempCount = 1
			else:
				self.reconnectionAttempCount += 1
			#Wait increasingly long between reconnection attempts, to give the server a chance to restart
			sleepTime = self.reconnectionAttempCount ** 3
			self.logger.info("Will try reconnecting to '{}' for attempt {} in {} seconds, max attempts is {}".format(
				self.serverfolder, self.reconnectionAttempCount, sleepTime, self.maxConnectionRetries if self.maxConnectionRetries else "not set"))
			gevent.sleep(sleepTime)
		#If we ever leave this loop, the bot is shut down. Unregister ourselves
		GlobalStore.bothandler.unregisterBot(self.serverfolder)

	def handleConnection(self):
		#Keep reading for possible incoming messages
		incomingData = ""
		# Set the timeout to 10 minutes, so if our computer loses connection to the server/internet, we notice
		self.ircSocket.settimeout(600)
		while True:
			try:
				incomingData += self.ircSocket.recv(2048)
			except gevent.socket.timeout:
				self.logger.warning("|{}| Our connection to the server timed out".format(self.serverfolder))
				return
			# A closed connection just makes recv return an empty string. Check for that
			if incomingData == "":
				self.logger.info("|{}| Server closed the connection".format(self.serverfolder))
				return
			# Handle all completely sent messages (delimited by \r\n), leave any unfinished messages for the next loop
			while '\r\n' in incomingData:
				line, incomingData = incomingData.split('\r\n', 1)
				# First deal with the simplest type of message, PING. Just reply PONG
				if line.startswith("PING"):
					self.sendLineToServer(line.replace("PING", "PONG", 1), False)
				else:
					# Let's find out what kind of message this is!
					lineParts = line.split(" ")
					# A line consists at least of 'source messageType [target] content'
					messageSource = lineParts[0]
					# It usually starts with a colon, remove that
					if messageSource.startswith(":"):
						messageSource = messageSource[1:]
					messageType = lineParts[1]
					#Convert numerical replies to human-readable ones, if applicable. Otherwise make it uppercase, since that's the standard
					messageType = Constants.IRC_NUMERIC_TO_NAME.get(messageType, messageType.upper())
					#The IRC protocol uses ':' to denote the start of a multi-word string. Join those here too, for easier parsing later
					messageParts = lineParts[2:]
					for messagePartIndex, messagePart in enumerate(messageParts):
						if messagePart.startswith(':'):
							#Join all the separate parts of the wordgroup, and remove the starting colon
							wordgroup = " ".join(messageParts[messagePartIndex:])[1:]
							messageParts[messagePartIndex] = wordgroup
							messageParts = messageParts[:messagePartIndex+1]
							break
					#Check if we have a function to deal with this type of message
					messageTypeFunction = getattr(self, "irc_" + messageType, None)
					if messageTypeFunction:
						messageTypeFunction(messageSource, messageParts)
					else:
						#No function for this type of message, fall back to a generic function
						self.irc_unknown_message_type(messageSource, messageType, lineParts)

	def irc_RPL_WELCOME(self, source, parameters):
		"""Called when we finished connecting to the server"""
		self.logger.info("|{}| Successfully connected".format(self.serverfolder))
		# We successfully connected, reset the reconnection count
		self.reconnectionAttempCount = None
		self.connectedAt = time.time()
		# Get the nickname we got assigned from the message
		self.nickname = parameters[0]
		if self.nickname != self.settings['nickname']:
			self.logger.info("|{} Nickname not available. Wanted '{}', got '{}'".format(self.serverfolder, self.settings['nickname'], self.nickname))
		# Inform all the modules that we connected
		message = IrcMessage("RPL_WELCOME", self, None, source, " ".join(parameters))
		GlobalStore.commandhandler.handleMessage(message)
		# Join the channels we should, if there are any
		if len(self.settings['joinChannels']) == 0:
			self.logger.info("|{}| No join channels specified, idling".format(self.serverfolder))
		else:
			for channel in self.settings['joinChannels']:
				self.joinChannel(channel)

	def joinChannel(self, channelname):
		if channelname[0] not in Constants.CHANNEL_PREFIXES:
			channelname = "#" + channelname
		if channelname in self.channelsUserList:
			self.logger.warning("|{}| Asked to join '{}' but I'm already there".format(self.serverfolder, channelname))
		else:
			self.sendLineToServer("JOIN {}".format(channelname))

	def leaveChannel(self, channelName, leaveMessage="Leaving..."):
		if channelName not in self.channelsUserList:
			self.logger.warning("|{}| Asked to leave '{}', but I'm not there".format(self.serverfolder, channelName))
		else:
			self.sendLineToServer("PART {} :{}".format(channelName, leaveMessage))

	def setNick(self, nickname):
		self.sendLineToServer("NICK " + nickname)

	def irc_ERR_NICKNAMEINUSE(self, source, parameters):
		# The nickname we want is apparently in use. Just append an underscore and try again
		newNicknameAttempt = parameters[1] + "_"
		self.logger.info("|{}| Requested nickname '{}' in use, retrying with nickname '{}'".format(self.serverfolder, parameters[1], newNicknameAttempt))
		self.nickname = newNicknameAttempt
		self.sendLineToServer("NICK " + newNicknameAttempt)

	#Create a list of user addresses per channel
	def retrieveChannelUsers(self, channel):
		self.isUpdatingChannelsUserList = True
		#Make sure we don't get duplicate data
		if channel in self.channelsUserList:
			self.channelsUserList.pop(channel)
		self.sendLineToServer("WHO {}".format(channel))

	def quit(self, quitMessage=None):
		self.shouldReconnect = False
		#If we are connected to a server, let it know we want to quit
		if self.connectedAt is not None:
			if quitMessage:
				self.sendLineToServer("QUIT :" + quitMessage)
			else:
				self.sendLineToServer("QUIT")
		# If we're not connected (yet?), stop trying to connect
		elif self.connectionManagerGreenlet:
			self.logger.info("|{}| Asked to quit, but not connected. Stopping wait before next connection attempt".format(self.serverfolder))
			self.connectionManagerGreenlet.kill()
			GlobalStore.bothandler.unregisterBot(self.serverfolder)
		else:
			#I don't know how we'd end up without a connection greenlet, but best handle it anyway
			self.logger.info("|{}| Asked to quit and don't have a connection greenlet. Unregistering".format(self.serverfolder))
			GlobalStore.bothandler.unregisterBot(self.serverfolder)

	#MESSAGE TYPE HANDLING FUNCTIONS
	def irc_unknown_message_type(self, source, messageType, messageParts):
		self.logger.info("|{}| Received unknown message type '{}' from {}: {}".format(self.serverfolder, messageType, source, " ".join(messageParts)))

	def ctcp_unknown_message_type(self, ctcpType, user, messageTarget, message):
		self.logger.info("|{}| Received unknown CTCP command '{}' on {} from {}, message '{}'".format(self.serverfolder, ctcpType, messageTarget, user, message))

	def irc_RPL_MOTD(self, prefix, params):
		self.messageLogger.log("Server message of the day: " + params[1])

	def irc_JOIN(self, prefix, params):
		"""Called when a user or the bot joins a channel"""
		# 'prefix' is the user, 'params' is a list with apparently just one entry, the channel
		message = IrcMessage('join', self, prefix, params[0])
		self.messageLogger.log("JOIN: {nick} ({address})".format(nick=message.userNickname, address=prefix), params[0])
		# If we just joined a channel, or if don't have a record of this channel yet, get all the users in it
		if message.userNickname == self.nickname or params[0] not in self.channelsUserList:
			self.retrieveChannelUsers(params[0])
		# If we don't know this user yet, add it to our list
		elif prefix not in self.channelsUserList[params[0]]:
			self.channelsUserList[params[0]].append(prefix)
		GlobalStore.commandhandler.handleMessage(message)

	def irc_PART(self, prefix, params):
		"""Called when a user or the bot leaves a channel"""
		# 'prefix' is the user, 'params' is a list with only the channel
		message = IrcMessage('part', self, prefix, params[0])
		self.messageLogger.log("PART: {nick} ({address})".format(nick=message.userNickname, address=prefix), params[0])
		# If a user parts before we have a proper channellist built, catch that error
		if params[0] not in self.channelsUserList:
			self.logger.warning("|{}| Unexpected PART, user '{}' parted from channel '{}' but we had no record of them".format(self.serverfolder, prefix, params[0]))
			# Schedule a rebuild of the userlist
			if not self.isUpdatingChannelsUserList:
				self.retrieveChannelUsers(params[0])
		# Keep track of the channels we're in
		elif message.userNickname == self.nickname:
			self.channelsUserList.pop(params[0])
		# Keep track of channel users
		elif prefix in self.channelsUserList[params[0]]:
			self.channelsUserList[params[0]].remove(prefix)
		GlobalStore.commandhandler.handleMessage(message)

	def irc_QUIT(self, prefix, params):
		"""Called when a user quits"""
		# 'prefix' is the user address, 'params' is a single-item list with the quit messages
		# log for every channel the user was in that they quit
		message = IrcMessage('quit', self, prefix, None, params[0])
		logMessage = "QUIT: {nick} ({address}): '{quitmessage}' ".format(nick=message.userNickname, address=prefix, quitmessage=params[0])
		for channel, userlist in self.channelsUserList.iteritems():
			if prefix in userlist:
				self.messageLogger.log(logMessage, channel)
				userlist.remove(prefix)
		GlobalStore.commandhandler.handleMessage(message)

	def irc_KICK(self, prefix, params):
		"""Called when a user is kicked"""
		# 'prefix' is the kicker, params[0] is the channel, params[1] is the user address of the kicked, params[-1] is the kick reason
		message = IrcMessage('kick', self, prefix, params[0], params[-1])
		kickedUserNick = params[1].split("!", 1)[0]
		self.messageLogger.log("KICK: {} was kicked by {}, reason: '{}'".format(kickedUserNick, message.userNickname, params[-1]), params[0])
		# Keep track of the channels we're in
		if kickedUserNick == self.nickname:
			if params[0] in self.channelsUserList:
				self.channelsUserList.pop(params[0])
		elif params[1] in self.channelsUserList[params[0]]:
			self.channelsUserList[params[0]].remove(params[1])
		GlobalStore.commandhandler.handleMessage(message)

	def irc_NICK(self, prefix, params):
		"""Called when a user or me change their nickname"""
		# 'prefix' is the full user address with the old nickname, params[0] is the new nickname
		message = IrcMessage('nickchange', self, prefix, None, params[0])
		oldnick = message.userNickname
		newnick = params[0]
		# New nick plus old address
		newaddress = newnick + "!" + prefix.split("!", 1)[1]
		# If it's about us, apparently a nick change was successful
		if oldnick == self.nickname:
			self.nickname = newnick
			self.logger.info("|{}| Our nick got changed from '{}' to '{}'".format(self.serverfolder, oldnick, self.nickname))
		# Log the change in every channel where it's relevant
		for channel, userlist in self.channelsUserList.iteritems():
			if prefix in userlist:
				# Update the userlists for all channels this user is in
				userlist.append(newaddress)
				userlist.remove(prefix)
				self.messageLogger.log("NICK CHANGE: {oldnick} changed their nick to {newnick}".format(oldnick=oldnick, newnick=newnick), channel)
		GlobalStore.commandhandler.handleMessage(message)

	def irc_MODE(self, prefix, params):
		#There are two possible MODE commands
		# The first is server-wide. Here the prefix is our user address, the first param is our username, and the second param is the mode change
		if len(params) == 2:
			self.logger.info("|{}| Our mode got set to '{}'".format(self.serverfolder, params[1]))
		# The second is channel-specific. Here the prefix is who or what is making the change, the first param is the channel,
		#  the second param is the mode change, and the rest of the params are the nicks whose mode got changed
		else:
			#It's not always a user making the change, check if it's a user address
			modeChanger = prefix
			if '!' in modeChanger:
				modeChanger = modeChanger.split('!', 1)[0]
			self.messageLogger.log("{} set mode to '{}' of user(s) {}".format(modeChanger, params[1], ", ".join(params[2:])), params[0])

	def irc_TOPIC(self, prefix, params):
		self.logger.debug("irc_TOPIC called, prefix is '{}', params is '{}'".format(prefix, params))

	def irc_RPL_TOPIC(self, prefix, params):
		self.logger.debug("irc_RPL_TOPIC called, prefix is '{}', params is '{}'".format(prefix, params))

	def irc_RPL_NOTOPIC(self, prefix, params):
		self.logger.debug("irc_RPL_NOTOPIC called, prefix is '{}', params is '{}'".format(prefix, params))

	def irc_RPL_WHOREPLY(self, prefix, params):
		#'prefix' is the server, 'params' is a list, with meaning [own_nick, channel, other_username, other_address, other_server, other_nick, flags, hops realname]
		# Flags can be H for active or G for away, and a * for oper, + for voiced
		if params[1] not in self.channelsUserList:
			self.channelsUserList[params[1]] = []
		self.channelsUserList[params[1]].append("{nick}!{username}@{address}".format(nick=params[5], username=params[2], address=params[3]))

	def irc_RPL_ENDOFWHO(self, prefix, params):
		self.isUpdatingChannelsUserList = False
		self.logger.info("|{}| Userlist for channels {} collected".format(self.serverfolder, ", ".join(self.channelsUserList.keys())))


	#CTCP FUNCTIONS
	def ctcp_ACTION(self, user, messageSource, messageText):
		self.handleMessage(user, messageSource, messageText, 'action')

	def ctcp_PING(self, user, messageSource, messageText):
		self.messageLogger.log("Received PING request from '{}'".format(user), messageSource)
		usernick = user.split('!', 1)[0]
		self.sendMessage(usernick, "PING " + messageText if messageText else str(time.time()), 'notice')

	def ctcp_SOURCE(self, user, messageSource, messageText):
		self.messageLogger.log("Received SOURCE request from '{}'".format(user), messageSource)
		usernick = user.split('!', 1)[0]
		self.sendMessage(usernick, "Thanks for the interest! You can read through my innards at https://github.com/Didero/DideRobot", 'notice')

	def ctcp_TIME(self, user, messageSource, messageText):
		self.messageLogger.log("Received TIME request from '{}'".format(user), messageSource)
		usernick = user.split('!', 1)[0]
		self.sendMessage(usernick, time.strftime("%a %d-%m-%Y %H:%M:%S %Z"), 'notice')

	def ctcp_VERSION(self, user, messageSource, messageText):
		self.messageLogger.log("Received VERSION request from '{}'".format(user), messageSource)
		usernick = user.split('!', 1)[0]
		self.sendMessage(usernick, "I don't have a set version, since I'm updated pretty frequently. I appreciate the interest though!", 'notice')


	#HUMAN COMMUNICATION FUNCTIONS
	def irc_PRIVMSG(self, user, messageParts):
		# First part of the messageParts is the channel the message came in from, or the user if it was a PM
		# Second part is the actual message
		messageSource = messageParts[0]
		# If the actual message (past the first colon) starts with 'chr(1)', it means it's a special CTCP message (like an action)
		if len(messageParts[1]) > 0 and messageParts[1][0] == Constants.CTCP_DELIMITER:
			#First section is the CTCP type
			# Make the type uppercase, since that's the standard (also means 'unknown_message_type' can't accidentally be called)
			ctcpType = messageParts[1].upper()
			messageText = None
			#Sometimes a message is appended (like for an ACTION), check for that
			if " " in ctcpType:
				ctcpType, messageText = messageParts[1].split(" ", 1)
				#The message could also end with a 'chr(1)', remove that
				if messageText.endswith(Constants.CTCP_DELIMITER):
					messageText = messageText[:-1]
			#The CTCP type could end with the CTCP delimiter
			if ctcpType.endswith(Constants.CTCP_DELIMITER):
				ctcpType = ctcpType[:-1]
			ctcpType = ctcpType[1:]  #Remove the CTCP delimiter
			#Check if we have a function to handle this type of CTCP message, otherwise fall back on a default
			ctcpFunction = getattr(self, "ctcp_" + ctcpType, None)
			if ctcpFunction:
				ctcpFunction(user, messageSource, messageText)
			else:
				self.ctcp_unknown_message_type(ctcpType, user, messageSource, messageText)
		#Normal message
		else:
			self.handleMessage(user, messageSource, messageParts[1], "say")

	def irc_NOTICE(self, user, messageParts):
		self.handleMessage(user, messageParts[0], messageParts[1], 'notice')

	def handleMessage(self, user, channel, messageText, messageType="say"):
		"""Called when the bot receives a message, which can be either in a channel or in a private message, as text or an action."""
		usernick = user.split("!", 1)[0]
		logsource = channel
		if channel == self.nickname or channel == '*':  #If a server wants to send a message to you before it knows your nick, it uses *
			logsource = usernick
		logtext = "({source}) "
		if messageType == 'say':
			logtext = "{user}: {message}"
		elif messageType == 'action':
			logtext = "*{user} {message}"
		elif messageType == 'notice':
			logtext = "[notice] {user}: {message}"

		self.messageLogger.log(logtext.format(user=usernick, message=messageText), logsource)
		message = IrcMessage(messageType, self, user, channel, messageText)
		#Let the CommandHandler see if a module needs to do something with this message
		GlobalStore.commandhandler.handleMessage(message)


	#SENDING OUT MESSAGES
	def sendLineToServer(self, lineToSend, shouldLogMessage=True):
		if not self.ircSocket:
			self.logger.error("|{}| Asked to send line '{}' to server, but socket closed".format(self.serverfolder, lineToSend))
			return
		if shouldLogMessage:
			self.logger.debug("|{}| > {}".format(self.serverfolder, lineToSend))
		self.ircSocket.send(lineToSend + "\r\n")

	def irc_ERR_NOTEXTTOSEND(self, prefix, params):
		self.logger.error("|{}| We just sent an empty line to the server, which is probably a bug in a module!".format(self.serverfolder))

	@staticmethod
	def formatCtcpMessage(ctcpType, messageText):
		return "{delim}{ctcpType} {msg}{delim}".format(delim=Constants.CTCP_DELIMITER, ctcpType=ctcpType, msg=messageText)

	def sendLineFromQueue(self):
		try:
			while True:
				#Verify that we're still connected and there is still a message in the queue, otherwise reset everything
				if self.connectedAt is None or not self.linesToSend or len(self.linesToSend) == 0:
					self.linesToSend = None
					self.lineSendingGreenlet = None
					break
				#Remove the first queued message and send it
				self.sendLineToServer(self.linesToSend.pop(0))
				#Keep going through the message queue until it's empty
				gevent.sleep(self.secondsBetweenLineSends)
		except gevent.GreenletExit:
			self.logger.info("|{}| Line sender greenlet was killed".format(self.serverfolder))

	def queueLineToSend(self, lineToSend):
		#If there's no rate limiting, there's no need for queueing either. Send the message now
		if not self.secondsBetweenLineSends:
			self.sendLineToServer(lineToSend)
			return
		#If there are no lines queued, the list is set to 'None'. Create a new list
		if not self.linesToSend:
			self.linesToSend = [lineToSend]
		# Add the message to the queue
		else:
			self.linesToSend.append(lineToSend)
		# If there's not yet a greenlet clearing the message queue, create one
		if not self.lineSendingGreenlet:
			self.lineSendingGreenlet = gevent.spawn(self.sendLineFromQueue)

	def sendMessage(self, target, messageText, messageType='say'):
		#Only say something if we're not muted, or if it's a private message or a notice
		if not self.isMuted or target[0] not in Constants.CHANNEL_PREFIXES or messageType == 'notice':
			#Make sure we're not trying to send Unicode
			if isinstance(messageText, unicode):
				try:
					messageText = messageText.encode(encoding='utf-8', errors='replace')
				except (UnicodeDecodeError, UnicodeEncodeError):
					self.logger.warning("|{}| [sendMessage] Error encoding message to string (is now type '{}'): '{}'".format(self.serverfolder, type(messageText), messageText))
			#It can't handle unicode message targets either
			if isinstance(target, unicode):
				target = target.encode('utf-8')
			logtext = ""
			messageCommand = "PRIVMSG"
			if messageType == 'action':
				#An action is just a special type of Say
				logtext += "*"
				messageText = self.formatCtcpMessage("ACTION", messageText)
			elif messageType == 'notice':
				logtext += "[notice] "
				messageCommand = "NOTICE"
			logtext += "{user}: {message}"
			line = "{} {} :{}".format(messageCommand, target, messageText)
			extraLines = None
			#Turn newlines in this line into multiple lines
			if '\n' in line or '\r' in line:
				extraLines = line.splitlines()
				line = extraLines.pop(0)
			#Check if the line isn't too long to send
			if len(line) >= Constants.MAX_LINE_LENGTH:
				if not extraLines:
					extraLines = [line[Constants.MAX_LINE_LENGTH:]]
				else:
					extraLines.insert(0, line[Constants.MAX_LINE_LENGTH:])
				line = line[:Constants.MAX_LINE_LENGTH]
			if target[0] not in Constants.CHANNEL_PREFIXES:
				#If it's a PM, bypass the message queue
				self.sendLineToServer(line)
			else:
				self.queueLineToSend(line)
			self.messageLogger.log(logtext.format(user=self.nickname, message=messageText), target)
			#Make sure any extra lines get sent too
			if extraLines:
				for extraLine in extraLines:
					self.sendMessage(target, extraLine, messageType)


	#USER LIST CHECKING FUNCTIONS
	def isUserAdmin(self, user, userNick=None, userAddress=None):
		return self.isUserInList(self.settings['admins'], user, userNick, userAddress)

	def shouldUserBeIgnored(self, user, userNick=None, userAddress=None):
		return self.isUserInList(self.settings['userIgnoreList'], user, userNick, userAddress)

	@staticmethod
	def isUserInList(userlist, user, userNick=None, userAddress=None):
		if user is None:
			return False
		if user in userlist or user.lower() in userlist:
			return True
		#If a usernick is provided, use that, otherwise split the full user address ourselves (if possible)
		if '!' not in user:
			return False
		if userNick is None or userAddress is None:
			userNick, userAddress = user.split('!', 1)
		if userNick in userlist or userNick.lower() in userlist or userAddress in userlist or userAddress.lower() in userlist:
			return True
		return False