예제 #1
0
    def __init__(
            self, server_list, nickname, realname,
            reconnection_interval=missing,
            recon=ExponentialBackoff(), **connect_params):
        super(SingleServerIRCBot, self).__init__()
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.server_list = [
            ServerSpec(*server)
            if isinstance(server, (tuple, list))
            else server
            for server in server_list
        ]
        assert all(
            isinstance(server, ServerSpec)
            for server in self.server_list
        )
        self.recon = recon
        # for compatibility
        if reconnection_interval is not missing:
            warnings.warn(
                "reconnection_interval is deprecated; "
                "pass a ReconnectStrategy object instead")
            self.recon = ExponentialBackoff(min_interval=reconnection_interval)

        self._nickname = nickname
        self._realname = realname
        for i in ["disconnect", "join", "kick", "mode",
                  "namreply", "nick", "part", "quit"]:
            self.connection.add_global_handler(
                i, getattr(self, "_on_" + i),
                -20)
예제 #2
0
파일: ircclient.py 프로젝트: yerejm/ttt
    def __init__(self, channel, nickname, server, port=6667, **connect_params):
        super(IRCClient, self).__init__()
        if server is None:
            raise Exception("IRC Server not provided")
        if nickname is None or " " in nickname:
            raise Exception("Invalid nickname: must be one word")
        if channel is None or " " in channel or "#" not in channel:
            raise Exception("Invalid channel: must be a word starting with #")
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.channel = channel
        self.server = ServerSpec(server, port)
        self._nickname = nickname
        self.clientname = socket.gethostname().split(".")[0]
        if 0 <= self.min_reconnect_wait:
            raise Exception("Minimum reconnect wait must be positive number")
        if self.min_reconnect_wait <= self.max_reconnect_wait:
            raise Exception(
                "Maximum reconnect wait must be larger than minimum")
        self._check_scheduled = False

        # Global handlers to handle channel/nick associations
        # Mostly when a nick is already in use
        for i in [
                "disconnect",
                "join",
                "kick",
                "mode",
                "namreply",
                "nick",
                "part",
                "quit",
        ]:
            self.connection.add_global_handler(i, getattr(self, "_on_" + i),
                                               -20)
예제 #3
0
 def __init__(self):
     self.userdict = IRCDict()
     self.operdict = IRCDict()
     self.voiceddict = IRCDict()
     self.ownerdict = IRCDict()
     self.halfopdict = IRCDict()
     self.modes = {}
예제 #4
0
    def __init__(self,
                 server_list,
                 nickname,
                 realname,
                 reconnection_interval=60,
                 **connect_params):
        """Constructor for SingleServerIRCBot objects.

        Arguments:

            server_list -- A list of ServerSpec objects or tuples of
                           parameters suitable for constructing ServerSpec
                           objects. Defines the list of servers the bot will
                           use (in order).

            nickname -- The bot's nickname.

            realname -- The bot's realname.

            reconnection_interval -- How long the bot should wait
                                     before trying to reconnect.

            dcc_connections -- A list of initiated/accepted DCC
            connections.

            **connect_params -- parameters to pass through to the connect
                                method.
        """

        super(SingleServerIRCBot, self).__init__()
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.server_list = [
            ServerSpec(*server) if isinstance(server,
                                              (tuple, list)) else server
            for server in server_list
        ]
        assert all(
            isinstance(server, ServerSpec) for server in self.server_list)
        if not reconnection_interval or reconnection_interval < 0:
            reconnection_interval = 2**31
        self.reconnection_interval = reconnection_interval

        self._nickname = nickname
        self._realname = realname
        for i in [
                "disconnect", "join", "kick", "mode", "namreply", "nick",
                "part", "quit"
        ]:
            self.connection.add_global_handler(i, getattr(self, "_on_" + i),
                                               -20)
예제 #5
0
 def __init__(self, logchannel=None, **kwargs):
   """Uses PanHandler automatically."""
   IRCClient.__init__(self, PanHandler, **kwargs)
   print "Successfully created", self
   self.channels = IRCDict()
   self.db = MongoClient(document_class=SON).mzpn
   self.logchan = logchannel
예제 #6
0
 def __init__(self, namreply=None):
   Channel.__init__(self)
   self.admindict = IRCDict()
   self.logdict = IRCDict()
   if namreply:
     for nick in namreply.split(' '):
       if nick[0] == '~': self.ownerdict[nick[1:]] = 1
       elif nick[0] == '&': self.admindict[nick[1:]] = 1
       elif nick[0] == '@': self.operdict[nick[1:]] = 1
       elif nick[0] =='%': self.halfopdict[nick[1:]] = 1
       elif nick[0] == '+': self.voicedict[nick[1:]] = 1
       else: 
         self.userdict[nick] = 1
         self.logdict[nick] = tempfile.NamedTemporaryFile(bufsize=5120)
         continue
       self.userdict[nick[1:]] = 1
       self.logdict[nick[1:]] = tempfile.NamedTemporaryFile(bufsize=5120)
예제 #7
0
    def __init__(self,
                 server_list,
                 channel_list,
                 nickname,
                 reconnection_interval=5,
                 **connect_params):
        """Constructor for IRCChatBot objects.

        Arguments:

            server_list -- A list of ServerSpec objects or tuples of
                           parameters suitable for constructing ServerSpec
                           objects. Defines the list of servers the bot will
                           use (in order).

            channel_list -- A list of channel names to join

            nickname -- The bot's nickname.

            reconnection_interval -- How long the bot should wait
                                     before trying to reconnect.

            **connect_params -- parameters to pass through to the connect
                                method.
        """

        super(IRCChatBot, self).__init__()
        self.connection.set_rate_limit(0.5)

        self.nickname = nickname
        self.__connect_params = connect_params

        self.channels = IRCDict()
        self.channel_list = channel_list

        self.server_list = [
            ServerSpec(*server) if isinstance(server,
                                              (tuple, list)) else server
            for server in server_list
        ]
        assert all(
            isinstance(server, ServerSpec) for server in self.server_list)

        if not reconnection_interval or reconnection_interval < 0:
            reconnection_interval = 2**31
        self.reconnection_interval = reconnection_interval
예제 #8
0
 def __init__(self, namreply=None):
     Channel.__init__(self)
     self.admindict = IRCDict()
     self.logdict = IRCDict()
     if namreply:
         for nick in namreply.split(' '):
             if nick[0] == '~':
                 self.ownerdict[nick[1:]] = 1
             elif nick[0] == '&':
                 self.admindict[nick[1:]] = 1
             elif nick[0] == '@':
                 self.operdict[nick[1:]] = 1
             elif nick[0] == '%':
                 self.halfopdict[nick[1:]] = 1
             elif nick[0] == '+':
                 self.voicedict[nick[1:]] = 1
             else:
                 self.userdict[nick] = 1
                 self.logdict[nick] = tempfile.NamedTemporaryFile(bufsize=5120)
                 continue
             self.userdict[nick[1:]] = 1
             self.logdict[nick[1:]] = tempfile.NamedTemporaryFile(bufsize=5120)
예제 #9
0
파일: ircclient.py 프로젝트: yerejm/ttt
    def __init__(self, channel, nickname, server, port=6667, **connect_params):
        super(IRCClient, self).__init__()
        if server is None:
            raise Exception("IRC Server not provided")
        if nickname is None or ' ' in nickname:
            raise Exception("Invalid nickname: must be one word")
        if channel is None or ' ' in channel or '#' not in channel:
            raise Exception("Invalid channel: must be a word starting with #")
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.channel = channel
        self.server = ServerSpec(server, port)
        self._nickname = nickname
        self.clientname = socket.gethostname().split('.')[0]
        assert 0 <= self.min_reconnect_wait <= self.max_reconnect_wait
        self._check_scheduled = False

        # Global handlers to handle channel/nick associations
        # Mostly when a nick is already in use
        for i in ["disconnect", "join", "kick", "mode",
                  "namreply", "nick", "part", "quit"]:
            self.connection.add_global_handler(
                i, getattr(self, "_on_" + i), -20
            )
예제 #10
0
class PanChan(Channel):
    """Uses the strings from namreply to set the values for each user in a channel."""

    def __init__(self, namreply=None):
        Channel.__init__(self)
        self.admindict = IRCDict()
        self.logdict = IRCDict()
        if namreply:
            for nick in namreply.split(' '):
                if nick[0] == '~':
                    self.ownerdict[nick[1:]] = 1
                elif nick[0] == '&':
                    self.admindict[nick[1:]] = 1
                elif nick[0] == '@':
                    self.operdict[nick[1:]] = 1
                elif nick[0] == '%':
                    self.halfopdict[nick[1:]] = 1
                elif nick[0] == '+':
                    self.voicedict[nick[1:]] = 1
                else:
                    self.userdict[nick] = 1
                    self.logdict[nick] = tempfile.NamedTemporaryFile(bufsize=5120)
                    continue
                self.userdict[nick[1:]] = 1
                self.logdict[nick[1:]] = tempfile.NamedTemporaryFile(bufsize=5120)

    def add_user(self, nick):
        self.logdict[nick] = tempfile.NamedTemporaryFile(bufsize=5120)
        Channel.add_user(self, nick)

    def clear_mode(self, mode, value=None):
        if mode == 'a':
            del self.admindict[value]
        else:
            Channel.clear_mode(self, mode, value)

    def change_nick(self, before, after):
        Channel.change_nick(self, before, after)
        self.logdict[after] = self.logdict[before]
        del self.logdict[before]

    def get_userlog(self, nick):
        if self.logdict.has_key(nick):
            log = self.logdict[nick]
            log.seek(0)
            return log.readlines()
        return None

    def has_privs(self, nick):
        return self.is_oper(nick) or self.is_halfop(nick) or self.isadmin or self.is_owner(nick)

    def handle_modes(self, modes, args):
        m = mod.match(modes)
        if m:
            l = list(args)
            s = m.group('mode1')
            if s[0] == '+':
                for each in s[1:]:
                    if each in 'qaohv' and l != []:
                        self.set_mode(each, l.pop(0))
                    else:
                        self.set_mode(each)
            else:
                for each in s[1:]:
                    if each in 'qaohv' and l != []:
                        self.clear_mode(each, l.pop(0))
                    else:
                        self.clear_mode(each)
            if m.group('mode2'):
                self.handle_modes(m.group('mode2'), *l)

    def is_admin(self, nick):
        return nick in self.admindict

    def log_user(self, nick, msg):
        log = self.logdict[nick]
        log.seek(0)
        lines = log.readlines()
        if len(lines) < LINE_LIMIT:
            log.write(msg + '\n')
        elif len(lines) == LINE_LIMIT:
            lines.pop(0)
            lines.append(msg + '\n')
            log.seek(0)
            log.writelines(*lines)
        log.flush()

    def set_mode(self, mode, value=None):
        if mode == 'a':
            self.admindict[value] = 1
        else:
            Channel.set_mode(self, mode, value)
예제 #11
0
 def __init__(self):
     self._users = IRCDict()
     self.mode_users = collections.defaultdict(IRCDict)
     self.modes = {}
예제 #12
0
class Channel(object):
    """
    A class for keeping information about an IRC channel.
    """

    user_modes = 'ovqha'
    """
    Modes which are applicable to individual users, and which
    should be tracked in the mode_users dictionary.
    """

    def __init__(self):
        self._users = IRCDict()
        self.mode_users = collections.defaultdict(IRCDict)
        self.modes = {}

    def users(self):
        """Returns an unsorted list of the channel's users."""
        return self._users.keys()

    def opers(self):
        """Returns an unsorted list of the channel's operators."""
        return self.mode_users['o'].keys()

    def voiced(self):
        """Returns an unsorted list of the persons that have voice
        mode set in the channel."""
        return self.mode_users['v'].keys()

    def owners(self):
        """Returns an unsorted list of the channel's owners."""
        return self.mode_users['q'].keys()

    def halfops(self):
        """Returns an unsorted list of the channel's half-operators."""
        return self.mode_users['h'].keys()

    def admins(self):
        """Returns an unsorted list of the channel's admins."""
        return self.mode_users['a'].keys()

    def has_user(self, nick):
        """Check whether the channel has a user."""
        return nick in self._users

    def is_oper(self, nick):
        """Check whether a user has operator status in the channel."""
        return nick in self.mode_users['o']

    def is_voiced(self, nick):
        """Check whether a user has voice mode set in the channel."""
        return nick in self.mode_users['v']

    def is_owner(self, nick):
        """Check whether a user has owner status in the channel."""
        return nick in self.mode_users['q']

    def is_halfop(self, nick):
        """Check whether a user has half-operator status in the channel."""
        return nick in self.mode_users['h']

    def is_admin(self, nick):
        """Check whether a user has admin status in the channel."""
        return nick in self.mode_users['a']

    def add_user(self, nick):
        self._users[nick] = 1

    @property
    def user_dicts(self):
        yield self._users
        for d in self.mode_users.values():
            yield d

    def remove_user(self, nick):
        for d in self.user_dicts:
            d.pop(nick, None)

    def change_nick(self, before, after):
        self._users[after] = self._users.pop(before)
        for mode_lookup in self.mode_users.values():
            if before in mode_lookup:
                mode_lookup[after] = mode_lookup.pop(before)

    def set_userdetails(self, nick, details):
        if nick in self._users:
            self._users[nick] = details

    def set_mode(self, mode, value=None):
        """Set mode on the channel.

        Arguments:

            mode -- The mode (a single-character string).

            value -- Value
        """
        if mode in self.user_modes:
            self.mode_users[mode][value] = 1
        else:
            self.modes[mode] = value

    def clear_mode(self, mode, value=None):
        """Clear mode on the channel.

        Arguments:

            mode -- The mode (a single-character string).

            value -- Value
        """
        try:
            if mode in self.user_modes:
                del self.mode_users[mode][value]
            else:
                del self.modes[mode]
        except KeyError:
            pass

    def has_mode(self, mode):
        return mode in self.modes

    def is_moderated(self):
        return self.has_mode("m")

    def is_secret(self):
        return self.has_mode("s")

    def is_protected(self):
        return self.has_mode("p")

    def has_topic_lock(self):
        return self.has_mode("t")

    def is_invite_only(self):
        return self.has_mode("i")

    def has_allow_external_messages(self):
        return self.has_mode("n")

    def has_limit(self):
        return self.has_mode("l")

    def limit(self):
        if self.has_limit():
            return self.modes["l"]
        else:
            return None

    def has_key(self):
        return self.has_mode("k")
예제 #13
0
 def _on_disconnect(self, c, e):
     self.channels = IRCDict()
     self.recon.run(self)
예제 #14
0
파일: ircclient.py 프로젝트: yerejm/ttt
class IRCClient(irc.client.SimpleIRCClient):
    """
    This class represents an IRC client that must have its poll() called
    periodically to process messages incoming from the IRC server. The client
    is limited in its ability to handle messages incoming from the IRC server
    and only has enough functionality to respond to PING events to maintain its
    connection.

    The primary role of the client is to act as a relay for the external caller
    calling poll() so that messages from that external caller are passed to the
    joined IRC channel.
    """

    # Heavily influenced by irc.client.SingleServerIRCBot and the example at
    # https://github.com/jaraco/irc/blob/master/scripts/testbot.py

    min_reconnect_wait = 1
    max_reconnect_wait = 10

    def __init__(self, channel, nickname, server, port=6667, **connect_params):
        super(IRCClient, self).__init__()
        if server is None:
            raise Exception("IRC Server not provided")
        if nickname is None or " " in nickname:
            raise Exception("Invalid nickname: must be one word")
        if channel is None or " " in channel or "#" not in channel:
            raise Exception("Invalid channel: must be a word starting with #")
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.channel = channel
        self.server = ServerSpec(server, port)
        self._nickname = nickname
        self.clientname = socket.gethostname().split(".")[0]
        if 0 <= self.min_reconnect_wait:
            raise Exception("Minimum reconnect wait must be positive number")
        if self.min_reconnect_wait <= self.max_reconnect_wait:
            raise Exception(
                "Maximum reconnect wait must be larger than minimum")
        self._check_scheduled = False

        # Global handlers to handle channel/nick associations
        # Mostly when a nick is already in use
        for i in [
                "disconnect",
                "join",
                "kick",
                "mode",
                "namreply",
                "nick",
                "part",
                "quit",
        ]:
            self.connection.add_global_handler(i, getattr(self, "_on_" + i),
                                               -20)

    def _on_disconnect(self, c, e):
        self.channels = IRCDict()
        self.reconnect()

    def reconnect(self):
        """
        Called on a disconnect event to start a reconnection. The actual
        reconnection is deferred for some random amount of seconds.
        """
        def check():
            self._check_scheduled = False
            if not self.connection.is_connected():
                self.reconnect()
                if self.connection.is_connected():
                    self.disconnect()
                self.connect()

        if self._check_scheduled:
            return
        reconnect_wait = max(self.min_reconnect_wait,
                             int(self.max_reconnect_wait * random()))
        self.reactor.scheduler.execute_after(reconnect_wait, check)
        self._check_scheduled = True

    def _on_join(self, c, e):
        ch = e.target
        nick = e.source.nick
        if nick == c.get_nickname():
            self.channels[ch] = Channel()
        self.channels[ch].add_user(nick)

    def _on_kick(self, c, e):
        nick = e.arguments[0]
        channel = e.target

        if nick == c.get_nickname():
            del self.channels[channel]
        else:
            self.channels[channel].remove_user(nick)

    def _on_mode(self, c, e):
        t = e.target
        if not irc.client.is_channel(t):
            # mode on self; disregard
            return
        ch = self.channels[t]

        modes = irc.modes.parse_channel_modes(" ".join(e.arguments))
        for sign, mode, argument in modes:
            f = {"+": ch.set_mode, "-": ch.clear_mode}[sign]
            f(mode, argument)

    def _on_namreply(self, c, e):
        """
        e.arguments[0] == "@" for secret channels,
                          "*" for private channels,
                          "=" for others (public channels)
        e.arguments[1] == channel
        e.arguments[2] == nick list
        """

        ch_type, channel, nick_list = e.arguments

        if channel == "*":
            # User is not in any visible channel
            # http://tools.ietf.org/html/rfc2812#section-3.2.5
            return

        for nick in nick_list.split():
            nick_modes = []

            if nick[0] in self.connection.features.prefix:
                nick_modes.append(self.connection.features.prefix[nick[0]])
                nick = nick[1:]

            for mode in nick_modes:
                self.channels[channel].set_mode(mode, nick)

            self.channels[channel].add_user(nick)

    def _on_nick(self, c, e):
        before = e.source.nick
        after = e.target
        for ch in self.channels.values():
            if ch.has_user(before):
                ch.change_nick(before, after)

    def _on_part(self, c, e):
        nick = e.source.nick
        channel = e.target

        if nick == c.get_nickname():
            del self.channels[channel]
        else:
            self.channels[channel].remove_user(nick)

    def _on_quit(self, c, e):
        nick = e.source.nick
        for ch in self.channels.values():
            if ch.has_user(nick):
                ch.remove_user(nick)

    def on_nicknameinuser(self, c, e):
        """
        When connecting it is discovered that the nick is already is use,
        provide an alternative.
        """
        c.nick(c.get_nickname() + "_")

    def on_welcome(self, c, e):
        """
        Automatically join the channel once welcomed by the server.
        """
        c.join(self.channel)
        self.say("{} has started on host {}".format(__progname__,
                                                    self.clientname))

    def on_privmsg(self, c, e):
        """
        Disable private messaging with users.
        """
        nick = e.source.nick
        msg = "I am sorry, {}; I do not do private messages.".format(nick)
        c.notice(nick, msg)

    def on_dccmsg(self, c, e):
        """
        Disable dcc messaging with users.
        """
        nick = e.source.nick
        # text = e.arguments[0].decode('utf-8')
        msg = "I am sorry, {}; I do not do dcc messages.".format(nick)
        c.privmsg(nick, msg)

    def disconnect(self):
        self.connection.disconnect("Bye!")

    def get_version(self):
        return "irc.client ({version})".format(
            version=irc.client.VERSION_STRING)

    def on_ctcp(self, c, e):
        nick = e.source.nick
        if e.arguments[0] == "VERSION":
            c.ctcp_reply(nick, "VERSION " + self.get_version())
        elif e.arguments[0] == "PING":
            if len(e.arguments) > 1:
                c.ctcp_reply(nick, "PING " + e.arguments[1])

    def on_pubmsg(self, c, e):
        nick = e.source.nick
        command = e.arguments[0]
        if "hello" in command or "hi" in command:
            self.say("Hello, {}".format(nick))
        elif "help" in command:
            self.say("{}, commands are: version".format(nick))
        elif "version" in command:
            self.say("{}, I am running {} {} using {} on {}".format(
                nick, __progname__, __version__, self.get_version(),
                self.clientname))

    def poll(self):
        """
        Poll the IRC connection for events. The reactor processing uses select
        on the socket so give a 0.2s timeout so that control returns.

        Polling must occur or else the IRC server will likely disconnect the
        client due to ping timeout.
        """
        self.reactor.process_once(0.2)

    def connect(self):
        server = self.server
        try:
            super(IRCClient, self).connect(server.host,
                                           server.port,
                                           self._nickname,
                                           server.password,
                                           username=self._nickname,
                                           ircname=self._nickname,
                                           **self.__connect_params)
        except irc.client.ServerConnectionError:
            self.reconnect()  # Schedule a deferred reconnection retry
            pass

    def say(self, message):
        """
        The main interface into this client (other than poll() and connect())
        to send messages from the external caller to the IRC channel.

        If there happens to be no connection when something is said, then say
        nothing. A (re)connection is either happening or the server is down.
        Note that this is an issue only because a message is sent to the IRC
        server explicitly and not a handler reacting to something the server
        has sent (which requires that the connection is up to occur).
        """
        try:
            self.connection.privmsg(self.channel, message)
        except irc.client.ServerNotConnectedError:
            # Connection down? Try a reconnect, and skip saying anything. The
            # time to say it has already passed.
            self.reconnect()
            pass
예제 #15
0
class SingleServerIRCBot(irc.client.SimpleIRCClient):
    """A single-server IRC bot class.

    The bot tries to reconnect if it is disconnected.

    The bot keeps track of the channels it has joined, the other
    clients that are present in the channels and which of those that
    have operator or voice modes.  The "database" is kept in the
    self.channels attribute, which is an IRCDict of Channels.

    Arguments:

        server_list -- A list of ServerSpec objects or tuples of
            parameters suitable for constructing ServerSpec
            objects. Defines the list of servers the bot will
            use (in order).

        nickname -- The bot's nickname.

        realname -- The bot's realname.

        recon -- A ReconnectStrategy for reconnecting on
            disconnect or failed connection.

        dcc_connections -- A list of initiated/accepted DCC
            connections.

        **connect_params -- parameters to pass through to the connect
            method.
    """
    def __init__(
            self, server_list, nickname, realname,
            reconnection_interval=missing,
            recon=ExponentialBackoff(), **connect_params):
        super(SingleServerIRCBot, self).__init__()
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.server_list = [
            ServerSpec(*server)
            if isinstance(server, (tuple, list))
            else server
            for server in server_list
        ]
        assert all(
            isinstance(server, ServerSpec)
            for server in self.server_list
        )
        self.recon = recon
        # for compatibility
        if reconnection_interval is not missing:
            warnings.warn(
                "reconnection_interval is deprecated; "
                "pass a ReconnectStrategy object instead")
            self.recon = ExponentialBackoff(min_interval=reconnection_interval)

        self._nickname = nickname
        self._realname = realname
        for i in ["disconnect", "join", "kick", "mode",
                  "namreply", "nick", "part", "quit"]:
            self.connection.add_global_handler(
                i, getattr(self, "_on_" + i),
                -20)

    def _connect(self):
        """
        Establish a connection to the server at the front of the server_list.
        """
        server = self.server_list[0]
        try:
            self.connect(
                server.host, server.port, self._nickname,
                server.password, ircname=self._realname,
                **self.__connect_params)
            self.connection_attempts = 1
        except irc.client.ServerConnectionError:
            pass

    def _on_disconnect(self, c, e):
        self.channels = IRCDict()
        self.recon.run(self)

    def _on_join(self, c, e):
        ch = e.target
        nick = e.source.nick
        if nick == c.get_nickname():
            self.channels[ch] = Channel()
        self.channels[ch].add_user(nick)

    def _on_kick(self, c, e):
        nick = e.arguments[0]
        channel = e.target

        if nick == c.get_nickname():
            del self.channels[channel]
        else:
            self.channels[channel].remove_user(nick)

    def _on_mode(self, c, e):
        t = e.target
        if not irc.client.is_channel(t):
            # mode on self; disregard
            return
        ch = self.channels[t]

        modes = irc.modes.parse_channel_modes(" ".join(e.arguments))
        for sign, mode, argument in modes:
            f = {"+": ch.set_mode, "-": ch.clear_mode}[sign]
            f(mode, argument)

    def _on_namreply(self, c, e):
        """
        e.arguments[0] == "@" for secret channels,
                          "*" for private channels,
                          "=" for others (public channels)
        e.arguments[1] == channel
        e.arguments[2] == nick list
        """

        ch_type, channel, nick_list = e.arguments

        if channel == '*':
            # User is not in any visible channel
            # http://tools.ietf.org/html/rfc2812#section-3.2.5
            return

        for nick in nick_list.split():
            nick_modes = []

            if nick[0] in self.connection.features.prefix:
                nick_modes.append(self.connection.features.prefix[nick[0]])
                nick = nick[1:]

            for mode in nick_modes:
                self.channels[channel].set_mode(mode, nick)

            self.channels[channel].add_user(nick)

    def _on_nick(self, c, e):
        before = e.source.nick
        after = e.target
        for ch in self.channels.values():
            if ch.has_user(before):
                ch.change_nick(before, after)

    def _on_part(self, c, e):
        nick = e.source.nick
        channel = e.target

        if nick == c.get_nickname():
            del self.channels[channel]
        else:
            self.channels[channel].remove_user(nick)

    def _on_quit(self, c, e):
        nick = e.source.nick
        for ch in self.channels.values():
            if ch.has_user(nick):
                ch.remove_user(nick)

    def die(self, msg="Bye, cruel world!"):
        """Let the bot die.

        Arguments:

            msg -- Quit message.
        """

        self.connection.disconnect(msg)
        sys.exit(0)

    def disconnect(self, msg="I'll be back!"):
        """Disconnect the bot.

        The bot will try to reconnect after a while.

        Arguments:

            msg -- Quit message.
        """
        self.connection.disconnect(msg)

    def get_version(self):
        """Returns the bot version.

        Used when answering a CTCP VERSION request.
        """
        return "Python irc.bot ({version})".format(
            version=irc.client.VERSION_STRING)

    def jump_server(self, msg="Changing servers"):
        """Connect to a new server, possibly disconnecting from the current.

        The bot will skip to next server in the server_list each time
        jump_server is called.
        """
        if self.connection.is_connected():
            self.connection.disconnect(msg)

        self.server_list.append(self.server_list.pop(0))
        self._connect()

    def on_ctcp(self, c, e):
        """Default handler for ctcp events.

        Replies to VERSION and PING requests and relays DCC requests
        to the on_dccchat method.
        """
        nick = e.source.nick
        if e.arguments[0] == "VERSION":
            c.ctcp_reply(nick, "VERSION " + self.get_version())
        elif e.arguments[0] == "PING":
            if len(e.arguments) > 1:
                c.ctcp_reply(nick, "PING " + e.arguments[1])
        elif (
            e.arguments[0] == "DCC"
                and e.arguments[1].split(" ", 1)[0] == "CHAT"):
            self.on_dccchat(c, e)

    def on_dccchat(self, c, e):
        pass

    def start(self):
        """Start the bot."""
        self._connect()
        super(SingleServerIRCBot, self).start()
예제 #16
0
파일: irc.py 프로젝트: danhetrick/qbit
    def __init__(self,
                 gui,
                 server,
                 port,
                 nickname,
                 password=None,
                 username=None,
                 ircname=None,
                 ssl=False):
        QThread.__init__(self)
        self.gui = gui
        self.server = server
        self.port = port
        self.nickname = nickname
        self.password = password
        self.username = username
        self.ircname = ircname
        self.ssl = ssl

        self.irc = None

        self.channels = IRCDict()

        self.cusers = defaultdict(list)

        self.blink_pubmsg = signal("pubmsg")
        self.blink_privmsg = signal("privmsg")
        self.blink_chanjoin = signal("join")
        self.blink_chanpart = signal("part")
        self.blink_registered = signal("registered")
        self.blink_gotnames = signal("names")
        self.blink_gotmotd = signal("motd")
        self.blink_nameend = signal("endnames")
        self.blink_mynick = signal("mynick")
        self.blink_gotmode = signal("mode")
        self.blink_gotumode = signal("umode")
        self.blink_gotnick = signal("newnick")
        self.blink_gotquit = signal("quit")
        self.blink_gotact = signal("action")
        self.blink_joinerror = signal("joinerror")
        self.blink_chanerror = signal("chanerror")
        self.blink_miscerror = signal("miscerror")
        self.blink_notice = signal("notice")
        self.blink_nicklength = signal("nicklen")
        self.blink_network = signal("network")
        self.blink_disconnected = signal("disconnected")
        self.blink_currenttopic = signal("currenttopic")
        self.blink_topicsetter = signal("topicsetter")
        self.blink_topic = signal("topic")

        self.blink_pubmsg.connect(self.pubchat)
        self.blink_privmsg.connect(self.privchat)
        self.blink_chanjoin.connect(self.join_channel)
        self.blink_chanpart.connect(self.part_channel)
        self.blink_registered.connect(self.welcome)
        self.blink_gotnames.connect(self.channel_users)
        self.blink_gotmotd.connect(self.motd)
        self.blink_nameend.connect(self.endnames)
        self.blink_mynick.connect(self.my_nick)
        self.blink_gotmode.connect(self.got_mode)
        self.blink_gotumode.connect(self.got_umode)
        self.blink_gotnick.connect(self.got_nick)
        self.blink_gotquit.connect(self.got_quit)
        self.blink_gotact.connect(self.got_action)
        self.blink_joinerror.connect(self.join_error)
        self.blink_chanerror.connect(self.chan_error)
        self.blink_miscerror.connect(self.misc_error)
        self.blink_notice.connect(self.got_notice)
        self.blink_nicklength.connect(self.got_nicklength)
        self.blink_network.connect(self.got_network)
        self.blink_disconnected.connect(self.got_disco)
        self.blink_currenttopic.connect(self.got_ctopic)
        self.blink_topicsetter.connect(self.got_stopic)
        self.blink_topic.connect(self.got_topic)
예제 #17
0
class Channel(object):
    """
    A class for keeping information about an IRC channel.
    """
    def __init__(self):
        self.userdict = IRCDict()
        self.operdict = IRCDict()
        self.voiceddict = IRCDict()
        self.ownerdict = IRCDict()
        self.halfopdict = IRCDict()
        self.modes = {}

    def users(self):
        """Returns an unsorted list of the channel's users."""
        return self.userdict.keys()

    def opers(self):
        """Returns an unsorted list of the channel's operators."""
        return self.operdict.keys()

    def voiced(self):
        """Returns an unsorted list of the persons that have voice
        mode set in the channel."""
        return self.voiceddict.keys()

    def owners(self):
        """Returns an unsorted list of the channel's owners."""
        return self.ownerdict.keys()

    def halfops(self):
        """Returns an unsorted list of the channel's half-operators."""
        return self.halfopdict.keys()

    def has_user(self, nick):
        """Check whether the channel has a user."""
        return nick in self.userdict

    def is_oper(self, nick):
        """Check whether a user has operator status in the channel."""
        return nick in self.operdict

    def is_voiced(self, nick):
        """Check whether a user has voice mode set in the channel."""
        return nick in self.voiceddict

    def is_owner(self, nick):
        """Check whether a user has owner status in the channel."""
        return nick in self.ownerdict

    def is_halfop(self, nick):
        """Check whether a user has half-operator status in the channel."""
        return nick in self.halfopdict

    def add_user(self, nick):
        self.userdict[nick] = 1

    def remove_user(self, nick):
        for d in self.userdict, self.operdict, self.voiceddict:
            if nick in d:
                del d[nick]

    def change_nick(self, before, after):
        self.userdict[after] = self.userdict.pop(before)
        if before in self.operdict:
            self.operdict[after] = self.operdict.pop(before)
        if before in self.voiceddict:
            self.voiceddict[after] = self.voiceddict.pop(before)

    def set_userdetails(self, nick, details):
        if nick in self.userdict:
            self.userdict[nick] = details

    def set_mode(self, mode, value=None):
        """Set mode on the channel.

        Arguments:

            mode -- The mode (a single-character string).

            value -- Value
        """
        if mode == "o":
            self.operdict[value] = 1
        elif mode == "v":
            self.voiceddict[value] = 1
        elif mode == "q":
            self.ownerdict[value] = 1
        elif mode == "h":
            self.halfopdict[value] = 1
        else:
            self.modes[mode] = value

    def clear_mode(self, mode, value=None):
        """Clear mode on the channel.

        Arguments:

            mode -- The mode (a single-character string).

            value -- Value
        """
        try:
            if mode == "o":
                del self.operdict[value]
            elif mode == "v":
                del self.voiceddict[value]
            elif mode == "q":
                del self.ownerdict[value]
            elif mode == "h":
                del self.halfopdict[value]
            else:
                del self.modes[mode]
        except KeyError:
            pass

    def has_mode(self, mode):
        return mode in self.modes

    def is_moderated(self):
        return self.has_mode("m")

    def is_secret(self):
        return self.has_mode("s")

    def is_protected(self):
        return self.has_mode("p")

    def has_topic_lock(self):
        return self.has_mode("t")

    def is_invite_only(self):
        return self.has_mode("i")

    def has_allow_external_messages(self):
        return self.has_mode("n")

    def has_limit(self):
        return self.has_mode("l")

    def limit(self):
        if self.has_limit():
            return self.modes["l"]
        else:
            return None

    def has_key(self):
        return self.has_mode("k")
예제 #18
0
 def on_disconnect(self, c, e):
     self.channels = IRCDict()
     self.reactor.scheduler.execute_after(self.reconnection_interval,
                                          self._connected_checker)
예제 #19
0
class IRCChatBot(SimpleIRCClient):
    def __init__(self,
                 server_list,
                 channel_list,
                 nickname,
                 reconnection_interval=5,
                 **connect_params):
        """Constructor for IRCChatBot objects.

        Arguments:

            server_list -- A list of ServerSpec objects or tuples of
                           parameters suitable for constructing ServerSpec
                           objects. Defines the list of servers the bot will
                           use (in order).

            channel_list -- A list of channel names to join

            nickname -- The bot's nickname.

            reconnection_interval -- How long the bot should wait
                                     before trying to reconnect.

            **connect_params -- parameters to pass through to the connect
                                method.
        """

        super(IRCChatBot, self).__init__()
        self.connection.set_rate_limit(0.5)

        self.nickname = nickname
        self.__connect_params = connect_params

        self.channels = IRCDict()
        self.channel_list = channel_list

        self.server_list = [
            ServerSpec(*server) if isinstance(server,
                                              (tuple, list)) else server
            for server in server_list
        ]
        assert all(
            isinstance(server, ServerSpec) for server in self.server_list)

        if not reconnection_interval or reconnection_interval < 0:
            reconnection_interval = 2**31
        self.reconnection_interval = reconnection_interval

    def _connected_checker(self):
        if not self.connection.is_connected():
            self.reactor.scheduler.execute_after(self.reconnection_interval,
                                                 self._connected_checker)
            self.jump_server()

    def _connect(self):
        server = self.server_list[0]
        try:
            self.connect(server.host,
                         server.port,
                         self.nickname,
                         server.password,
                         ircname=self.nickname,
                         **self.__connect_params)
        except ServerConnectionError:
            pass

    def on_disconnect(self, c, e):
        self.channels = IRCDict()
        self.reactor.scheduler.execute_after(self.reconnection_interval,
                                             self._connected_checker)

    def on_join(self, c, e):
        ch = e.target
        nick = e.source.nick
        if nick == c.get_nickname():
            self.channels[ch] = Channel()
        self.channels[ch].add_user(nick)
        lg.debug('IRCChatBot::on_join: %s =====>>> %s', nick, ch)

    def on_part(self, c, e):
        ch = e.target
        nick = e.source.nick
        lg.debug('IRCChatBot::on_part: %s <<<===== %s', nick, ch)
        if nick == c.get_nickname():
            del self.channels[ch]
        else:
            self.channels[ch].remove_user(nick)

    def on_nick(self, c, e):
        before = e.source.nick
        after = e.target
        for ch in self.channels.values():
            if ch.has_user(before):
                ch.change_nick(before, after)
                print('%s changed nickname to %s' % (before, after))

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")

    def on_welcome(self, c, e):
        for ch in self.channel_list:
            c.join(ch)

    def on_privmsg(self, c, e):
        nick = e.source.nick
        text = ' '.join(e.arguments)

        lg.debug('IRCChatBot::on_privmsg(): %s: %s', nick, text)

        # ignore my own message
        if nick == self.username:
            return None

        # commands must start with +
        if text[0] != '+':
            return None

        now = datetime.utcnow().replace(tzinfo=pytz.utc)
        msg = {
            'created_utc': calendar.timegm(now.utctimetuple()),
            'author': {
                'name': nick
            },
            'channel': e.target
        }
        msg['id'] = str(msg['created_utc'])
        msg['body'] = text
        print(msg)

        action = ctb_action.eval_message(ctb_misc.DotDict(msg), self.ctb)
        if action:
            lg.info("IRCChatBot::on_pubmsg(): %s from %s", action.type,
                    action.u_from.name)
            lg.debug("IRCChatBot::on_pubmsg(): comment body: <%s>",
                     action.msg.body)
            action.do()

    def on_pubmsg(self, c, e):
        ch = e.target
        nick = e.source.nick
        text = ' '.join(e.arguments)

        lg.debug('IRCChatBot::on_pubmsg(): %s on %s: %s', nick, ch, text)

        # ignore my own message
        if nick == self.username:
            return None

        # commands must start with +
        if text[0] != '+':
            return None

        now = datetime.utcnow().replace(tzinfo=pytz.utc)
        msg = {
            'created_utc': calendar.timegm(now.utctimetuple()),
            'author': {
                'name': nick
            }
        }
        msg['id'] = str(msg['created_utc'])
        msg['body'] = text
        print(msg)

        action = ctb_action.eval_message(ctb_misc.DotDict(msg), self.ctb)
        if action:
            lg.info("IRCChatBot::on_pubmsg(): %s from %s", action.type,
                    action.u_from.name)
            lg.debug("IRCChatBot::on_pubmsg(): comment body: <%s>",
                     action.msg.body)
            action.do()

    def disconnect(self, msg="disconnecting"):
        self.connection.disconnect(msg)

    def jump_server(self, msg="switching server"):
        """Connect to a new server, possibly disconnecting from the current.

        The bot will skip to next server in the server_list each time
        jump_server is called.
        """
        if self.connection.is_connected():
            self.connection.disconnect(msg)

        self.server_list.append(self.server_list.pop(0))
        self._connect()

    def send_msg(self, recipient, msg):
        self.connection.privmsg(recipient, msg)

    def start(self):
        self._connect()
        super(IRCChatBot, self).start()
예제 #20
0
class DixieBot(irc.bot.SingleServerIRCBot):

    # Class-level variables which form attributes.  These all refer to aspects
    # of the bot.
    joined_channels = IRCDict()
    canonical_name = ""
    nick = ""
    owner = ""

    # Connection information.
    server = ""
    port = 0

    # The bot's owner's authentication password.
    password = ""

    # Is the bot's owner authenticated or not?
    authenticated = ""

    # Whether or not the connection is SSL/TLS encrypted or not.
    usessl = ""

    # Response engine's hostname and port.
    engine = ""

    # Bot's API key to interface with the response engine.
    api_key = ""

    # One instance of wordfilter.Wordfilter() to rule them all...
    wordfilter = None

    # Whether or not to use the conversation engine to respond?
    respond = None

    # Whether or not the bot's owner can speak through the bot by using
    # private messages.  By default, the bot doesn't let you do that.
    ghost = None

    # Methods on the connection object to investigate:
    # connect() - Connect to a server?
    # connected() -
    # disconnect() -
    # get_nickname() -
    # get_server_name() -
    # info() -
    # ircname() -
    # is_connected() - See if the connection is still up?
    # part() - Leave channel?
    # privmsg() - Send privmsg?
    # quit() - Terminate IRC connection?
    # reconnect() - Reconnect to server?
    # send_raw() -
    # stats() -
    # time() -

    def __init__(self, channels, nickname, server, port, owner, usessl,
                 password, engine_host, engine_port, api_key, respond):

        # Initialize the class' attributes.
        for i in channels:
            self.joined_channels[i] = 1
        self.canonical_name = nick
        self.nick = nick
        self.owner = owner
        self.server = server
        self.port = port
        self.password = password
        self.authenticated = False
        self.usessl = usessl
        self.engine = 'http://' + engine_host + ':' + engine_port
        self.api_key = api_key
        self.wordfilter = Wordfilter()
        self.respond = respond
        self.ghost = False

        # Connection factory object handle.
        factory = ""

        # If SSL/TLS support is requested, pass the ssl.wrap_socket() method
        # as a keyword argument.
        if self.usessl:
            logger.debug("Constructing SSL/TLS server connector.")
            factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
        else:
            logger.debug("Constructing plaintext server connector.")
            factory = irc.connection.Factory()

        # Initialize an instance of this class by running the parent class'
        # default initializer method.
        #
        # [(server, port)] can be a list of one or more (server, port) tuples
        # because it can connect to more than one at once.
        # The other two arguments are the bot's nickname and realname.
        logger.debug("Instantiating SingleServerIRCBot superclass.")
        irc.bot.SingleServerIRCBot.__init__(self, [(self.server, self.port)],
                                            self.nick,
                                            self.nick,
                                            connect_factory=factory)
        logger.debug("Channels configured for this bot:")
        logger.debug("  " + str(self.joined_channels))

    # This method fires if the configured nickname is already in use.  If that
    # happens, change the bot's nick slightly.
    # Note that the name of this method is specifically what the irc module
    # looks for.
    def on_nicknameinuse(self, connection, event):
        logger.info("Bot nickname " + self.nick +
                    " is already taken.  Falling back to bot nickname " +
                    self.nick + "_.")
        connection.privmsg(
            self.owner, self.nick +
            " seems to be taken already.  Falling back to nickname " +
            self.nick + "_.")
        connection.nick(connection.get_nickname() + "_")

    # This method fires when the server accepts the bot's connection.  It walks
    # through the IRCDict of channels and tries to join each one.
    def on_welcome(self, connection, event):
        logger.debug("Entered DixieBot.on_welcome().")
        for channel in self.joined_channels:
            logger.debug("Trying to join channel " + channel + ".")
            connection.join(channel)
            logger.info("Joined channel " + channel + ".")
            connection.privmsg(self.owner, "Joined " + channel + ".")

            # Just to be silly, roll 1d10.  On a 1, say hello to the channel.
            roll = random.randint(1, 10)
            if roll == 1:
                pause = random.randint(1, 10)
                time.sleep(pause)
                logger.debug("Bot has randomly decided to announce itself.")
                connection.privmsg(
                    channel, "Hey, bro!  I'm " + self.nick +
                    ", the best cowboy who ever punched deck!")
        logger.debug("Exiting DixieBot.on_welcome().")

    # This method fires if the bot gets kicked from a channel.  The smart
    # thing to do is sleep for a random period of time (between one and three
    # minutes) before trying to join again.
    def on_kick(self, connection, event):
        delay = random.randint(60, 180)
        logger.debug("Got kicked from " + event.target + ".  Sleeping for " +
                     str(delay) + " seconds.")
        connection.privmsg(
            self.owner, "Got kicked from " + event.target +
            ".  Sleeping for " + str(delay) + " seconds.")
        time.sleep(delay)
        logger.debug("Rejoining channel " + event.target + ".")
        connection.privmsg(self.owner,
                           "Rejoining channel " + event.target + ".")
        connection.join(event.target)
        logger.info("Successfully re-joined channel " + event.target + ".")
        connection.privmsg(
            self.owner, "Successfully re-joined channel " + event.target + ".")
        return

    # This method fires if the bot gets kickbanned.
    def on_bannedfromchan(self, connection, event):
        logger.warn("Uh-oh - I got kickbanned from " + event.target +
                    ".  I know when I'm not wanted.")
        self.privmsg(
            self.owner, "Uh-oh - I got kickbanned from " + event.target +
            ".  I know when I'm not wanted.")
        self.joined_channels.remove(event.target)
        return

    # This method fires when the server disconnects the bot for some reason.
    # Ideally, the bot should try to connect again after a random number of
    # seconds.
    def on_disconnect(self, connection, event):
        delay = random.randint(60, 180)
        logger.warn("Connection dropped from server " + self.server +
                    ".  Sleeping for " + str(delay) + " seconds.")
        time.sleep(delay)
        logger.warn("Reconnecting to server " + self.server + " on port " +
                    str(self.port) + ".")
        try:
            irc.bot.SingleServerIRCBot.connect(self,
                                               [(self.server, self.port)],
                                               self.nick, self.nick)
            logger.info("Successfully reconnected to server " + self.server +
                        ".")
        except:
            logger.warn("Unable to reconnect to " + self.server +
                        ".  Something's really wrong.")

    # This method fires when the bot receives a private message.  For the
    # moment, if it's the bot's owner always learn from the text because this
    # is an ideal way to get more interesting stuff into the bot's brain.
    # It'll make a good place to look for and respond to specific commands,
    # too.
    def on_privmsg(self, connection, line):

        # IRC nick that sent a line to the bot in private chat.
        sending_nick = line.source.split("!~")[0]

        # Line of text sent from the channel or private message.
        irc_text = line.arguments[0]

        # String that holds what may or may not be a channel name.
        possible_channel_name = None

        # String that may or may not hold a respond to a channel in ghost mode.
        irc_response = None

        # Handle to an HTTP request object.
        http_connection = ""

        # JSON document containing responses from the conversation engine.
        json_response = {}

        # See if the owner is authenticating to the bot.
        if "!auth " in irc_text:
            self._authenticate(connection, sending_nick, irc_text)
            return

        # Handle messages from the bot's owner (if authenticated).
        if sending_nick == self.owner:
            if not self.authenticated:
                connection.privmsg(sending_nick, "You're not authenticated.")
                return

            # If the owner asks for online help, provide it.
            if irc_text == "!help" or irc_text == "!commands":
                self._help(connection, sending_nick)
                return

            # See if the owner is asking the bot to self-terminate.
            if irc_text == "!quit":
                logger.info("The bot's owner has told it to shut down.")
                connection.privmsg(sending_nick,
                                   "I get the hint.  Shuttin' down.")
                sys.exit(0)

            # See if the owner is asking for the bot's current configuration.
            if irc_text == "!config":
                self._current_config(connection, sending_nick)
                return

            # See if the owner is asking the bot to ping the conversation
            # engine's server.
            if irc_text == "!ping":
                self._ping(connection, sending_nick)
                return

            # See if the owner is asking the bot to change its nick.
            if "!nick" in irc_text:
                self._nick(connection, irc_text, sending_nick)
                return

            # See if the owner is asking the bot to join a channel.
            if "!join " in irc_text:
                self._join(connection, irc_text, sending_nick)
                return

            # See if the owner is flipping the self.respond flag.
            if "!respond" in irc_text:
                self._respond(connection, irc_text, sending_nick)
                return

            # See if the owner is asking for help on ghost mode.
            if "!ghosthelp" in irc_text:
                self._ghost_help(connection, sending_nick)
                return

            # See if the owner is flipping the self.ghost flag.
            if "!ghost" in irc_text:
                self._ghost_mode(connection, sending_nick)
                return

            # If the bot's in ghost mode, determine whether or not the bot's
            # owner has sent text destined for a channel the bot's sitting in.
            # If this is the case, send the channel the text sent by the
            # bot's owner.
            possible_channel_name = irc_text.split()[0]
            logger.debug("Value of possible_channel_name: " +
                         possible_channel_name)
            if self.ghost:
                if "#" in possible_channel_name:

                    # Test to see if the bot is in the channel in question.
                    in_channel = False
                    for channel in self.joined_channels:
                        if channel == possible_channel_name:
                            in_channel = True
                            break
                    if not in_channel:
                        logger.debug("Not in channel " +
                                     possible_channel_name + ".")
                        connection.privmsg(
                            sending_nick, "I'm not in channel " +
                            possible_channel_name + ".")
                        return
                    logger.debug("In channel " + possible_channel_name + ".")

                    # Send the text to the channel.
                    irc_response = " ".join(irc_text.split()[1:])
                    logger.debug("Value of irc_response: " + irc_response)
                    connection.privmsg(possible_channel_name, irc_response)

            # Always learn from private messages from the bot's owner.  Do not
            # respond to them if the bot's in ghost mode.  Determine whether
            # or not a #channelname is at the head of the text and if so
            # elide it by setting the line of text from the IRC channel to
            # the IRC response which already has the #channelname removed.
            if "#" in possible_channel_name:
                irc_text = " ".join(irc_text.split()[1:])
                logger.debug(
                    "Got a possible channel name.  Set value of irc_text to: "
                    + str(irc_text))

            # Train the bot on text sent by the bot's owner.
            json_response = json.loads(self._teach_brain(irc_text))
            if json_response['id'] != 200:
                logger.warn(
                    "DixieBot.on_privmsg(): Conversation engine returned error code "
                    + str(json_response['id']) + ".")

            # Don't get responses when in ghost mode.
            if self.ghost:
                return

            # Get a response for text sent by the bot's owner.
            json_response = json.loads(self._get_response(irc_text))
            if json_response['id'] != 200:
                logger.warn(
                    "DixieBot.on_privmsg(): Conversation engine returned error code "
                    + str(json_response['id']) + ".")
                return

            # Send the response text back to the bot's owner.
            connection.privmsg(sending_nick, json_response['response'])
            return
        else:
            logger.debug(
                "Somebody messaged me.  The content of the message was: " +
                irc_text)

    # Helper method for authenticating the bot's owner.
    def _authenticate(self, connection, nick, text):
        logger.warn("IRC user " + nick +
                    " is attempting to authenticate to the bot.")
        if self.password in text:
            connection.privmsg(nick,
                               "Authentication confirmed.  Welcome back.")
            self.owner = nick
            self.authenticated = True
            return
        else:
            connection.privmsg(nick, "Incorrect.")
            return

    # Helper method that implements online help.
    def _help(self, connection, nick):
        connection.privmsg(nick, "Here are the commands I support:")
        connection.privmsg(
            nick, "!help and !commands - You're reading them right now.")
        connection.privmsg(nick, "!quit - Shut me down.")
        connection.privmsg(
            nick, "!auth - Authenticate your current IRC nick as my admin.")
        connection.privmsg(nick, "!config - Send my current configuration.")
        connection.privmsg(
            nick,
            "!ping - Ping the conversation engine to make sure I can contact it."
        )
        connection.privmsg(nick,
                           "!nick <new nick> - Try to change my IRC nick.")
        connection.privmsg(nick, "!join <channel> - Join a channel.")
        connection.privmsg(
            nick, "!respond - Toggle respond/don't respond to users flag.")
        connection.privmsg(nick,
                           "!ghosthelp - Get online help for ghost mode.")
        connection.privmsg(
            nick,
            "!ghost - Whether or not the bot's registered owner can remotely interact with a channel the bot's a member of using the bot as a client."
        )
        return

    # Helper method that tells the bot's owner what the bot's current runtime
    # configuration is.
    def _current_config(self, connection, nick):
        connection.privmsg(nick, "Here's my current runtime configuration.")
        connection.privmsg(nick, "Channels I'm connected to: ")
        for channel in self.joined_channels:
            connection.privmsg(nick, "  " + channel)
        connection.privmsg(nick, "Current nick: " + self.nick)
        connection.privmsg(
            nick,
            "Canonical name (for interacting with the conversation engine): " +
            self.canonical_name)
        connection.privmsg(
            nick,
            "Server and port: " + self.server + " " + str(self.port) + "/tcp")
        if self.usessl:
            connection.privmsg(nick,
                               "My connection to the server is encrypted.")
        else:
            connection.privmsg(nick,
                               "My connection to the server isn't encrypted.")
        if self.respond:
            connection.privmsg(nick, "I respond to people talking to me.")
        else:
            connection.privmsg(nick,
                               "I don't respond to people talking to me.")
        if self.ghost:
            connection.privmsg(nick,
                               "I am monitoring IRC channels in ghost mode.")
        else:
            connection.privmsg(nick, "I am not in ghost mode.")
        return

    # Helper method that pings the bot's conversation engine.  I realize that
    # doing this is probably a little weird, but seeing as how I'm splitting
    # everything else out into helper methods to make adding functionality
    # later on easier I may as well.
    def _ping(self, connection, nick):
        connection.privmsg(nick, "Pinging the conversation engine...")
        http_connection = requests.get(self.engine + "/ping")
        if http_connection.text == "pong":
            connection.privmsg(nick, "I can hit the conversation engine.")
        else:
            connection.privmsg(
                nick,
                "I don't seem to be able to reach the conversation engine.")
        return

    # Helper method that will allow the bot to change its nick.
    def _nick(self, connection, text, nick):
        connection.privmsg(nick, "Trying to change my IRC nick...")
        self.nick = text.split()[1].strip()
        connection.nick(self.nick)
        logger.debug("New IRC nick: " + self.nick)
        connection.privmsg(nick, "Done.")
        return

    # Helper method that will allow the bot to join a channel.
    def _join(self, connection, text, nick):
        new_channel = text.split()[1].strip()
        connection.privmsg(nick, "Trying to join channel " + new_channel + ".")
        logger.debug("Trying to join channel " + new_channel + ".")
        connection.join(new_channel)
        self.joined_channels[new_channel] = 1
        connection.privmsg(nick, "Joined " + new_channel + ".")
        return

    # Helper method that flips the bot's mode from "respond when spoken to" to
    # don't respond when spoken to.
    def _respond(self, connection, text, nick):
        if self.respond == True:
            self.respond = False
            logger.info("Turn off the bot's auto-response mode.")
            connection.privmsg(nick,
                               "I won't respond to people talking to me.")
            return
        if self.respond == False:
            self.respond = True
            logger.info("Turn on the bot's auto-response mode.")
            connection.privmsg(nick, "Now responding to people talking to me.")
            return

    # Send the user online help for ghost mode.
    def _ghost_help(self, connection, nick):
        connection.privmsg(
            nick,
            "Ghost mode lets you interact with any channel I'm sitting in remotely so you don't have to join it."
        )
        connection.privmsg(
            nick,
            "This is ideal if you want to maintain a certain degree of stealth."
        )
        connection.privmsg(
            nick,
            "I can join the channel from one server and interact with everyone like a bot, and you can connect from another server without joining any channels, !auth to me, and communicate through me."
        )
        connection.privmsg(
            nick,
            "If I get rumbled, I get bounced and your disposable server can be banned, and all you have to do is get a copy of my conversation engine to preserve me.  You should be okay."
        )
        connection.privmsg(
            nick,
            "Please note that if you have me join a number of busy channels you may not be able to keep up with all the traffic, so choose the channels I join wisely.  Keep the number small for best results."
        )
        connection.privmsg(
            nick,
            "Put the name of the channel you want me to send text to at the front of a private message, like this:"
        )
        connection.privmsg(nick, "/msg botname")
        connection.privmsg(nick, "#somechannel Hello, world.")
        connection.privmsg(
            nick,
            "I will send activity in the channel back to you via the same privmsg as long as you're authenticated."
        )
        return

    # Flips the ghost mode flag.
    def _ghost_mode(self, connection, nick):
        if self.ghost == False:
            self.ghost = True
            logger.info("Ghost mode now activated.")
            connection.privmsg(nick, "Ghost mode activated.")
            connection.privmsg(
                nick,
                "You can now interact with the following channels through me: "
            )
            for channel in self.joined_channels:
                connection.privmsg(nick, "  " + channel)
            return
        if self.ghost == True:
            self.ghost = False
            logger.info("Ghost mode now deactivated.")
            connection.privmsg(nick, "Ghost mode deactivated.")
            return

    # This method fires every time a public message is posted to an IRC
    # channel.  Technically, 'line' should be 'event' but I'm just now getting
    # this module figured out...
    def on_pubmsg(self, connection, line):
        # JSON document from the conversation engine.
        json_response = {}

        # IRC nick that sent a line to the channel.
        sending_nick = line.source.split("!~")[0]
        logger.debug("Sending nick: " + sending_nick)

        # Line of text sent from the channel.
        irc_text = line.arguments[0]

        # If the line is from the bot's owner, learn from it and then decide
        # whether to respond or not.  Just in case somebody grabs the nick of
        # the bot's owner, don't respond if they're not authenticated (because
        # that could go real bad, real fast...)
        if sending_nick == self.owner and self.authenticated:

            # If the bot's owner addressed it directly, always respond.  Just
            # make sure to remove the bot's nick from the text to minimize
            # spurious entries in the bot's brain.
            asked_directly = irc_text.split(':')[0].strip()
            if asked_directly == self.nick:
                logger.debug(
                    "The bot's owner addressed the construct directly.  This is a special case."
                )

                # Extract the dialogue from the text in the IRC channel.
                dialogue_text = irc_text.split(':')[1].strip()

                # Send a request to train the conversation engine on the text.
                logger.debug("Training engine on text: " + dialogue_text)
                json_response = json.loads(self._teach_brain(dialogue_text))
                if json_response['id'] != int(200):
                    logger.warn(
                        "DixieBot.on_pubmsg(): Conversation engine returned error code "
                        + str(json_response['id']) + ".")
                    return

                # If the bot is in ghost mode, do not respond.
                if self.ghost:
                    return

                # Get a response to the text from the channel.
                json_response = json.loads(self._get_response(irc_text))
                if json_response['id'] != int(200):
                    logger.warn(
                        "DixieBot.on_pubmsg(): Conversation engine returned error code "
                        + str(json_response['id']) + ".")
                    return

                # Send the reply to the channel.
                connection.privmsg(line.target, json_response['response'])
                return

            # Otherwise, just learn from the bot's owner.
            json_response = json.loads(self._teach_brain(irc_text))
            if json_response['id'] != int(200):
                logger.warn(
                    "DixieBot.on_pubmsg(): Conversation engine returned error code "
                    + str(json_response['id']) + ".")
                return

            # Check the respond/don't respond flag.  If it's set to False,
            # don't say anything.
            if not self.respond:
                return

            # If the respond/don't respond flag it set to True, decide if the
            # bot is going to respond or not.  To be polite to people, only
            # respond 5% of the time.  10% was too much.
            roll = random.randint(1, 100)
            if roll <= 5:
                json_response = json.loads(self._get_response(irc_text))
                if json_response['id'] != int(200):
                    logger.warn(
                        "DixieBot.on_pubmsg(): Conversation engine returned error code "
                        + str(json_response['id']) + ".")
                    return

                # connection.privmsg() can be used to send text to either a
                # channel or a user.
                # Send the response.
                connection.privmsg(line.target, json_response['response'])
            return

        # If the line is not from the bot's owner, and the bot is in ghost
        # mode, relay the line to the bot's owner via privmsg.
        if self.ghost and self.authenticated:
            logger.debug("Relaying a line of text from " + line.target +
                         " to the bot's owner.")
            connection.privmsg(self.owner, line.target + ":: " + irc_text)

        # If the line is not from the bot's owner, decide randomly if the bot
        # should learn from it, or learn from and respond to it.  Respect the
        # respond/don't respond flag.
        roll = random.randint(1, 10)
        if roll == 1:
            logger.debug("Learning from the last line seen in the channel.")
            if self.wordfilter.blacklisted(irc_text):
                logger.warn("Wordfilter: Nope nope nope...")
                return
            json_response = json.loads(self._teach_brain(irc_text))
            if json_response['id'] != int(200):
                logger.warn(
                    "DixieBot.on_pubmsg(): Conversation engine returned error code "
                    + str(json_response['id']) + ".")
            return

        if roll == 2:
            logger.debug(
                "Learning from the last line seen in the channel.  I might respond to it."
            )
            if self.wordfilter.blacklisted(irc_text):
                logger.warn("Wordfilter: Nope nope nope...")
                return
            json_response = json.loads(self._teach_brain(irc_text))
            if json_response['id'] != int(200):
                logger.warn(
                    "DixieBot.on_pubmsg(): Conversation engine returned error code "
                    + str(json_response['id']) + ".")
                return

            # Check the respond/don't respond flag.  If it's set to False,
            # don't say anything.
            if not self.respond:
                return

            # Get and send a response.
            json_response = json.loads(self._get_response(irc_text))
            if json_response['id'] != int(200):
                logger.warn(
                    "DixieBot.on_pubmsg(): Conversation engine returned error code "
                    + str(json_response['id']) + ".")
                return
            connection.privmsg(line.target, json_response['response'])
            return

    # This method should fire when a client in the current channel emits a QUIT
    # event relayed by the server.  It detects the bot's owner disconnecting
    # and deauthenticates them.
    def on_quit(self, connection, event):
        sending_nick = event.source.split("!~")[0]
        if event.type == "quit" and sending_nick == self.owner and self.authenticated:
            logger.info("The bot's owner has disconnected.  Deauthenticating.")
            self.authenticated = False
            connection.privmsg(line.target, "Seeya, boss.")
            return

    # Sends text to train the conversation engine on.
    def _teach_brain(self, text):

        # Custom headers required by the conversation engine.
        headers = {"Content-Type": "application/json"}

        # HTTP request object handle.
        http_request = ""

        # JSON documents sent to and received from the conversation engine.
        json_request = {}
        json_request['botname'] = self.canonical_name
        json_request['apikey'] = self.api_key
        json_request['stimulus'] = text
        json_response = {}

        # Make an HTTP request to the conversation engine.
        http_request = requests.put(self.engine + "/learn",
                                    headers=headers,
                                    data=json.dumps(json_request))
        json_response = json.loads(http_request.content)
        return json_response

    # Gets a response from the conversation engine.  Return a response.
    def _get_response(self, text):

        # Custom headers required by the conversation engine.
        headers = {"Content-Type": "application/json"}

        # HTTP request object handle.
        http_request = ""

        # Response to send to the channel or user.
        response = ""

        # JSON documents sent to and received from the conversation engine.
        json_request = {}
        json_request['botname'] = self.canonical_name
        json_request['apikey'] = self.api_key
        json_request['stimulus'] = text
        json_response = {}

        # Contact the conversation engine to get a response.
        http_request = requests.get(self.engine + "/response",
                                    headers=headers,
                                    data=json.dumps(json_request))
        json_response = json.loads(http_request.content)
        return json_response
예제 #21
0
파일: ircclient.py 프로젝트: yerejm/ttt
class IRCClient(irc.client.SimpleIRCClient):
    """
    This class represents an IRC client that must have its poll() called
    periodically to process messages incoming from the IRC server. The client
    is limited in its ability to handle messages incoming from the IRC server
    and only has enough functionality to respond to PING events to maintain its
    connection.

    The primary role of the client is to act as a relay for the external caller
    calling poll() so that messages from that external caller are passed to the
    joined IRC channel.
    """
    # Heavily influenced by irc.client.SingleServerIRCBot and the example at
    # https://github.com/jaraco/irc/blob/master/scripts/testbot.py

    min_reconnect_wait = 1
    max_reconnect_wait = 10

    def __init__(self, channel, nickname, server, port=6667, **connect_params):
        super(IRCClient, self).__init__()
        if server is None:
            raise Exception("IRC Server not provided")
        if nickname is None or ' ' in nickname:
            raise Exception("Invalid nickname: must be one word")
        if channel is None or ' ' in channel or '#' not in channel:
            raise Exception("Invalid channel: must be a word starting with #")
        self.__connect_params = connect_params
        self.channels = IRCDict()
        self.channel = channel
        self.server = ServerSpec(server, port)
        self._nickname = nickname
        self.clientname = socket.gethostname().split('.')[0]
        assert 0 <= self.min_reconnect_wait <= self.max_reconnect_wait
        self._check_scheduled = False

        # Global handlers to handle channel/nick associations
        # Mostly when a nick is already in use
        for i in ["disconnect", "join", "kick", "mode",
                  "namreply", "nick", "part", "quit"]:
            self.connection.add_global_handler(
                i, getattr(self, "_on_" + i), -20
            )

    def _on_disconnect(self, c, e):
        self.channels = IRCDict()
        self.reconnect()

    def reconnect(self):
        """
        Called on a disconnect event to start a reconnection. The actual
        reconnection is deferred for some random amount of seconds.
        """
        def check():
            self._check_scheduled = False
            if not self.connection.is_connected():
                self.reconnect()
                if self.connection.is_connected():
                    self.disconnect()
                self.connect()

        if self._check_scheduled:
            return
        reconnect_wait = max(
            self.min_reconnect_wait,
            int(self.max_reconnect_wait * random())
        )
        self.reactor.scheduler.execute_after(reconnect_wait, check)
        self._check_scheduled = True

    def _on_join(self, c, e):
        ch = e.target
        nick = e.source.nick
        if nick == c.get_nickname():
            self.channels[ch] = Channel()
        self.channels[ch].add_user(nick)

    def _on_kick(self, c, e):
        nick = e.arguments[0]
        channel = e.target

        if nick == c.get_nickname():
            del self.channels[channel]
        else:
            self.channels[channel].remove_user(nick)

    def _on_mode(self, c, e):
        t = e.target
        if not irc.client.is_channel(t):
            # mode on self; disregard
            return
        ch = self.channels[t]

        modes = irc.modes.parse_channel_modes(" ".join(e.arguments))
        for sign, mode, argument in modes:
            f = {"+": ch.set_mode, "-": ch.clear_mode}[sign]
            f(mode, argument)

    def _on_namreply(self, c, e):
        """
        e.arguments[0] == "@" for secret channels,
                          "*" for private channels,
                          "=" for others (public channels)
        e.arguments[1] == channel
        e.arguments[2] == nick list
        """

        ch_type, channel, nick_list = e.arguments

        if channel == '*':
            # User is not in any visible channel
            # http://tools.ietf.org/html/rfc2812#section-3.2.5
            return

        for nick in nick_list.split():
            nick_modes = []

            if nick[0] in self.connection.features.prefix:
                nick_modes.append(self.connection.features.prefix[nick[0]])
                nick = nick[1:]

            for mode in nick_modes:
                self.channels[channel].set_mode(mode, nick)

            self.channels[channel].add_user(nick)

    def _on_nick(self, c, e):
        before = e.source.nick
        after = e.target
        for ch in self.channels.values():
            if ch.has_user(before):
                ch.change_nick(before, after)

    def _on_part(self, c, e):
        nick = e.source.nick
        channel = e.target

        if nick == c.get_nickname():
            del self.channels[channel]
        else:
            self.channels[channel].remove_user(nick)

    def _on_quit(self, c, e):
        nick = e.source.nick
        for ch in self.channels.values():
            if ch.has_user(nick):
                ch.remove_user(nick)

    def on_nicknameinuser(self, c, e):
        """
        When connecting it is discovered that the nick is already is use,
        provide an alternative.
        """
        c.nick(c.get_nickname() + "_")

    def on_welcome(self, c, e):
        """
        Automatically join the channel once welcomed by the server.
        """
        c.join(self.channel)
        self.say('ttt has started on host {}'.format(self.clientname))

    def on_privmsg(self, c, e):
        """
        Disable private messaging with users.
        """
        nick = e.source.nick
        msg = "I am sorry, {}; I do not do private messages.".format(nick)
        c.notice(nick, msg)

    def on_dccmsg(self, c, e):
        """
        Disable dcc messaging with users.
        """
        nick = e.source.nick
        # text = e.arguments[0].decode('utf-8')
        msg = "I am sorry, {}; I do not do dcc messages.".format(nick)
        c.privmsg(nick, msg)

    def disconnect(self):
        self.connection.disconnect("Bye!")

    def get_version(self):
        return "irc.client ({version})".format(
            version=irc.client.VERSION_STRING)

    def on_ctcp(self, c, e):
        nick = e.source.nick
        if e.arguments[0] == "VERSION":
            c.ctcp_reply(nick, "VERSION " + self.get_version())
        elif e.arguments[0] == "PING":
            if len(e.arguments) > 1:
                c.ctcp_reply(nick, "PING " + e.arguments[1])

    def on_pubmsg(self, c, e):
        nick = e.source.nick
        command = e.arguments[0]
        if 'hello' in command or 'hi' in command:
            self.say("Hello, {}".format(nick))
        elif 'help' in command:
            self.say("Commands are: version".format(nick))
        elif 'version' in command:
            self.say("I am running ttt {} using {} on {}".format(
                __version__,
                self.get_version(),
                self.clientname
            ))

    def poll(self):
        """
        Poll the IRC connection for events. The reactor processing uses select
        on the socket so give a 0.2s timeout so that control returns.

        Polling must occur or else the IRC server will likely disconnect the
        client due to ping timeout.
        """
        self.reactor.process_once(0.2)

    def connect(self):
        server = self.server
        try:
            super(IRCClient, self).connect(
                server.host,
                server.port,
                self._nickname,
                server.password,
                username=self._nickname,
                ircname=self._nickname,
                **self.__connect_params
            )
        except irc.client.ServerConnectionError:
            self.reconnect()  # Schedule a deferred reconnection retry
            pass

    def say(self, message):
        """
        The main interface into this client (other than poll() and connect())
        to send messages from the external caller to the IRC channel.

        If there happens to be no connection when something is said, then say
        nothing. A (re)connection is either happening or the server is down.
        Note that this is an issue only because a message is sent to the IRC
        server explicitly and not a handler reacting to something the server
        has sent (which requires that the connection is up to occur).
        """
        try:
            self.connection.privmsg(self.channel, message)
        except irc.client.ServerNotConnectedError:
            # Connection down? Try a reconnect, and skip saying anything. The
            # time to say it has already passed.
            self.reconnect()
            pass
예제 #22
0
class PanChan(Channel):
  """Uses the strings from namreply to set the values for each user in a channel."""
  def __init__(self, namreply=None):
    Channel.__init__(self)
    self.admindict = IRCDict()
    self.logdict = IRCDict()
    if namreply:
      for nick in namreply.split(' '):
        if nick[0] == '~': self.ownerdict[nick[1:]] = 1
        elif nick[0] == '&': self.admindict[nick[1:]] = 1
        elif nick[0] == '@': self.operdict[nick[1:]] = 1
        elif nick[0] =='%': self.halfopdict[nick[1:]] = 1
        elif nick[0] == '+': self.voicedict[nick[1:]] = 1
        else: 
          self.userdict[nick] = 1
          self.logdict[nick] = tempfile.NamedTemporaryFile(bufsize=5120)
          continue
        self.userdict[nick[1:]] = 1
        self.logdict[nick[1:]] = tempfile.NamedTemporaryFile(bufsize=5120)

  def add_user(self, nick):
    self.logdict[nick] = tempfile.NamedTemporaryFile(bufsize=5120)
    Channel.add_user(self, nick)

  def clear_mode(self, mode, value=None):
    if mode == 'a':
      del self.admindict[value]
    else:
      Channel.clear_mode(self, mode, value)
  
  def change_nick(self, before, after):
    Channel.change_nick(self, before, after)
    self.logdict[after] = self.logdict[before]
    del self.logdict[before]

  def get_userlog(self, nick):
    if self.logdict.has_key(nick):
      log = self.logdict[nick]
      log.seek(0)
      return log.readlines()
    return None

  def has_privs(self, nick):
    return self.is_oper(nick) or self.is_halfop(nick) or self.isadmin or self.is_owner(nick)

  def handle_modes(self, modes, args):
    m = mod.match(modes)
    if m:
      l = list(args)
      s = m.group('mode1')
      if s[0] == '+':
        for each in s[1:]:
          if each in 'qaohv' and l != []:
            self.set_mode(each, l.pop(0))
          else:
            self.set_mode(each)
      else:
        for each in s[1:]:
          if each in 'qaohv' and l != []:
            self.clear_mode(each, l.pop(0))
          else:
            self.clear_mode(each)
      if m.group('mode2'):
        self.handle_modes(m.group('mode2'), *l)

  def is_admin(self, nick):
    return nick in self.admindict

  def log_user(self, nick, msg):
    log = self.logdict[nick]
    log.seek(0)
    lines = log.readlines()
    if len(lines) < LINE_LIMIT:
      log.write(msg + '\n')
    elif len(lines) == LINE_LIMIT:
      lines.pop(0)
      lines.append(msg + '\n')
      log.seek(0)
      log.writelines(*lines)
    log.flush()

  def set_mode(self, mode, value=None):
    if mode == 'a':
      self.admindict[value] = 1
    else:
      Channel.set_mode(self, mode, value)
예제 #23
0
파일: ircclient.py 프로젝트: yerejm/ttt
 def _on_disconnect(self, c, e):
     self.channels = IRCDict()
     self.reconnect()
예제 #24
0
 def _on_disconnect(self, c, e):
     self.channels = IRCDict()
     self.connection.execute_delayed(self.reconnection_interval,
                                     self._connected_checker)