コード例 #1
0
 def handle_privmsg(self, source, command, args):
     # Convert nicks to UIDs, where they exist.
     target = self._getUid(args[0])
     # We use lowercase channels internally, but uppercase UIDs.
     if utils.isChannel(target):
         target = self.irc.toLower(target)
     return {'target': target, 'text': args[1]}
コード例 #2
0
ファイル: corecommands.py プロジェクト: Ro9ueAdmin/PyLink
def identify(irc, source, args):
    """<username> <password>

    Logs in to PyLink using the configured administrator account."""
    if utils.isChannel(irc.called_in):
        irc.reply('Error: This command must be sent in private. '
                '(Would you really type a password inside a channel?)')
        return
    try:
        username, password = args[0], args[1]
    except IndexError:
        irc.reply('Error: Not enough arguments.')
        return

    # Process new-style accounts.
    if login.checkLogin(username, password):
        _login(irc, source, username)
        return

    # Process legacy logins (login:user).
    if username.lower() == conf.conf['login'].get('user', '').lower() and password == conf.conf['login'].get('password'):
        realuser = conf.conf['login']['user']
        _login(irc, source, realuser)
    else:
        # Username not found.
        _loginfail(irc, source, username)
コード例 #3
0
ファイル: clientbot.py プロジェクト: IotaSpencer/PyLink
    def mode(self, source, channel, modes, ts=None):
        """Sends channel MODE changes."""
        if utils.isChannel(channel):
            extmodes = []
            # Re-parse all channel modes locally to eliminate anything invalid, such as unbanning
            # things that were never banned. This prevents the bot from getting caught in a loop
            # with IRCd MODE acknowledgements.
            # FIXME: More related safety checks should be added for this.
            log.debug('(%s) mode: re-parsing modes %s', self.irc.name, modes)
            joined_modes = self.irc.joinModes(modes)
            for modepair in self.irc.parseModes(channel, joined_modes):
                log.debug('(%s) mode: checking if %s a prefix mode: %s',
                          self.irc.name, modepair, self.irc.prefixmodes)
                if modepair[0][-1] in self.irc.prefixmodes:
                    if self.irc.isInternalClient(modepair[1]):
                        # Ignore prefix modes for virtual internal clients.
                        log.debug(
                            '(%s) mode: skipping virtual client prefixmode change %s',
                            self.irc.name, modepair)
                        continue
                    else:
                        # For other clients, change the mode argument to nick instead of PUID.
                        nick = self.irc.getFriendlyName(modepair[1])
                        log.debug('(%s) mode: coersing mode %s argument to %s',
                                  self.irc.name, modepair, nick)
                        modepair = (modepair[0], nick)
                extmodes.append(modepair)

            log.debug('(%s) mode: filtered modes for %s: %s', self.irc.name,
                      channel, extmodes)
            if extmodes:
                self.irc.send('MODE %s %s' %
                              (channel, self.irc.joinModes(extmodes)))
コード例 #4
0
    def mode(self, numeric, target, modes, ts=None):
        """
        Sends mode changes from a PyLink client/server. The mode list should be
        a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.
        """
        # <- :unreal.midnight.vpn MODE #test +ntCo GL 1444361345

        if (not self.irc.isInternalClient(numeric)) and \
                (not self.irc.isInternalServer(numeric)):
            raise LookupError('No such PyLink client/server exists.')

        self.irc.applyModes(target, modes)
        joinedmodes = self.irc.joinModes(modes)
        if utils.isChannel(target):
            # The MODE command is used for channel mode changes only
            ts = ts or self.irc.channels[self.irc.toLower(target)].ts
            self._send(numeric, 'MODE %s %s %s' % (target, joinedmodes, ts))
        else:
            # For user modes, the only way to set modes (for non-U:Lined servers)
            # is through UMODE2, which sets the modes on the caller.
            # U:Lines can use SVSMODE/SVS2MODE, but I won't expect people to
            # U:Line a PyLink daemon...
            if not self.irc.isInternalClient(target):
                raise ProtocolError(
                    'Cannot force mode change on external clients!')
            self._send(target, 'UMODE2 %s' % joinedmodes)
コード例 #5
0
ファイル: clientbot.py プロジェクト: IotaSpencer/PyLink
    def handle_mode(self, source, command, args):
        """Handles MODE changes."""
        # <- :[email protected] MODE #dev +v ice
        # <- :ice MODE ice :+Zi
        target = args[0]
        if utils.isChannel(target):
            target = self.irc.toLower(target)
            oldobj = self.irc.channels[target].deepcopy()
        else:
            target = self.irc.nickToUid(target)
            oldobj = None
        modes = args[1:]
        changedmodes = self.irc.parseModes(target, modes)
        self.irc.applyModes(target, changedmodes)

        if self.irc.isInternalClient(target):
            log.debug(
                '(%s) Suppressing MODE change hook for internal client %s',
                self.irc.name, target)
            return
        if changedmodes:
            # Prevent infinite loops: don't send MODE hooks if the sender is US.
            # Note: this is not the only check in Clientbot to prevent mode loops: if our nick
            # somehow gets desynced, this may not catch everything it's supposed to.
            if (self.irc.pseudoclient and source != self.irc.pseudoclient.uid
                ) or not self.irc.pseudoclient:
                return {
                    'target': target,
                    'modes': changedmodes,
                    'channeldata': oldobj
                }
コード例 #6
0
ファイル: ts6.py プロジェクト: pombredanne/PyLink
    def mode(self, numeric, target, modes, ts=None):
        """Sends mode changes from a PyLink client/server."""
        # c <- :0UYAAAAAA TMODE 0 #a +o 0T4AAAAAC
        # u <- :0UYAAAAAA MODE 0UYAAAAAA :-Facdefklnou

        if (not self.irc.isInternalClient(numeric)) and \
                (not self.irc.isInternalServer(numeric)):
            raise LookupError('No such PyLink client/server exists.')

        self.irc.applyModes(target, modes)
        modes = list(modes)

        if utils.isChannel(target):
            ts = ts or self.irc.channels[self.irc.toLower(target)].ts
            # TMODE:
            # parameters: channelTS, channel, cmode changes, opt. cmode parameters...

            # On output, at most ten cmode parameters should be sent; if there are more,
            # multiple TMODE messages should be sent.
            while modes[:10]:
                # Seriously, though. If you send more than 10 mode parameters in
                # a line, charybdis will silently REJECT the entire command!
                joinedmodes = self.irc.joinModes(modes = [m for m in modes[:10] if m[0] not in self.irc.cmodes['*A']])
                modes = modes[10:]
                self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes))
        else:
            joinedmodes = self.irc.joinModes(modes)
            self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
コード例 #7
0
ファイル: ircs2s_common.py プロジェクト: Ro9ueAdmin/PyLink
    def handle_privmsg(self, source, command, args):
        """Handles incoming PRIVMSG/NOTICE."""
        # TS6:
        # <- :70MAAAAAA PRIVMSG #dev :afasfsa
        # <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
        # P10:
        # <- ABAAA P AyAAA :privmsg text
        # <- ABAAA O AyAAA :notice text
        target = self._getUid(args[0])

        # Coerse =#channel from Charybdis op moderated +z to @#channel.
        if target.startswith('='):
            target = '@' + target[1:]

        # We use lowercase channels internally, but uppercase UIDs.
        # Strip the target of leading prefix modes (for targets like @#channel)
        # before checking whether it's actually a channel.
        split_channel = target.split('#', 1)
        if len(split_channel) >= 2 and utils.isChannel('#' + split_channel[1]):
            # Note: don't mess with the case of the channel prefix, or ~#channel
            # messages will break on RFC1459 casemapping networks (it becomes ^#channel
            # instead).
            target = '#'.join(
                (split_channel[0], self.irc.toLower(split_channel[1])))
            log.debug('(%s) Normalizing channel target %s to %s',
                      self.irc.name, args[0], target)

        return {'target': target, 'text': args[1]}
コード例 #8
0
ファイル: automode.py プロジェクト: IotaSpencer/PyLink
def getChannelPair(irc, source, chanpair, perm=None):
    """
    Fetches the network and channel given a channel pair,
    also optionally checking the caller's permissions.
    """
    log.debug('(%s) Looking up chanpair %s', irc.name, chanpair)
    try:
        network, channel = chanpair.split('#')
    except ValueError:
        raise ValueError("Invalid channel pair %r" % chanpair)
    channel = '#' + channel
    channel = irc.toLower(channel)

    assert utils.isChannel(channel), "Invalid channel name %s." % channel

    if network:
        ircobj = world.networkobjects.get(network)
    else:
        ircobj = irc

    assert ircobj, "Unknown network %s" % network

    if perm is not None:
        # Only check for permissions if we're told to and the irc object exists.
        if ircobj.name != irc.name:
            perm = 'remote' + perm

        checkAccess(irc, source, channel, perm)

    return (ircobj, channel)
コード例 #9
0
    def mode(self, numeric, target, modes, ts=None):
        """Sends mode changes from a PyLink client/server."""
        # c <- :0UYAAAAAA TMODE 0 #a +o 0T4AAAAAC
        # u <- :0UYAAAAAA MODE 0UYAAAAAA :-Facdefklnou

        if (not self.irc.isInternalClient(numeric)) and \
                (not self.irc.isInternalServer(numeric)):
            raise LookupError('No such PyLink client/server exists.')

        self.irc.applyModes(target, modes)
        modes = list(modes)

        if utils.isChannel(target):
            ts = ts or self.irc.channels[self.irc.toLower(target)].ts
            # TMODE:
            # parameters: channelTS, channel, cmode changes, opt. cmode parameters...

            # On output, at most ten cmode parameters should be sent; if there are more,
            # multiple TMODE messages should be sent.
            msgprefix = ':%s TMODE %s %s ' % (numeric, ts, target)
            bufsize = S2S_BUFSIZE - len(msgprefix)

            for modestr in self.irc.wrapModes(modes,
                                              bufsize,
                                              max_modes_per_msg=10):
                self.irc.send(msgprefix + modestr)
        else:
            joinedmodes = self.irc.joinModes(modes)
            self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
コード例 #10
0
def identify(irc, source, args):
    """<username> <password>

    Logs in to PyLink using the configured administrator account."""
    if utils.isChannel(irc.called_in):
        irc.reply('Error: This command must be sent in private. '
                  '(Would you really type a password inside a channel?)')
        return
    try:
        username, password = args[0], args[1]
    except IndexError:
        irc.reply('Error: Not enough arguments.')
        return
    # Usernames are case-insensitive, passwords are NOT.
    if username.lower() == irc.conf['login']['user'].lower(
    ) and password == irc.conf['login']['password']:
        realuser = irc.conf['login']['user']
        irc.users[source].identified = realuser
        irc.reply('Successfully logged in as %s.' % realuser)
        log.info("(%s) Successful login to %r by %s", irc.name, username,
                 irc.getHostmask(source))
    else:
        irc.reply('Error: Incorrect credentials.')
        u = irc.users[source]
        log.warning("(%s) Failed login to %r from %s", irc.name, username,
                    irc.getHostmask(source))
コード例 #11
0
def mimic(irc, source, args):
    """<channel> <log glob>

    Echoes chatlogs matching the log glob to the given channel. Home folders ("~") and environment variables ($HOME, etc.) are expanded in THAT order."""
    irc.checkAuthenticated(source, allowOper=False)
    try:
        channel, logs = args[:2]
    except ValueError:
        irc.reply("Error: Not enough arguments. Needs 2: channel, log glob.")
        return
    else:
        assert utils.isChannel(channel), "Invalid channel %s" % channel
        channel = irc.toLower(channel)

        # Expand variables in the path glob
        logs = os.path.expandvars(os.path.expanduser(logs))

    mysid = irc.proto.spawnServer(HOSTNAME)
    logs = sorted(glob.glob(logs))

    def talk():
        userdict = {}
        for item in logs:
            irc.proto.notice(irc.pseudoclient.uid, channel,
                             'Beginning mimic of log file %s' % item)
            with open(item, errors='replace'
                      ) as f:  # errors='replace' ignores UnicodeDecodeError
                for line in f.readlines():
                    action = False  # Marks whether line is an action
                    match = CHAT_REGEX.search(line)
                    actionmatch = ACTION_REGEX.search(line)

                    if actionmatch:
                        action = True

                    # Only one of the two matches need to exist for the check below
                    match = match or actionmatch

                    if match:
                        sender, text = match.group(1, 2)
                        # Update the user dict returned by _sayit(), which automatically spawns users
                        # as they're seen.
                        userdict = _sayit(irc,
                                          mysid,
                                          userdict,
                                          channel,
                                          sender,
                                          text,
                                          action=action)

                # Pause for a bit to prevent excess flood.
                time.sleep(LINEDELAY)
        else:
            # Once we're done, SQUIT everyone to clean up automagically.
            irc.proto.notice(irc.pseudoclient.uid, channel,
                             'Finished mimic of %s items' % len(logs))
            irc.proto.squit(irc.sid, mysid)

    threading.Thread(target=talk).start()
コード例 #12
0
ファイル: unreal.py プロジェクト: IotaSpencer/PyLink
 def knock(self, numeric, target, text):
     """Sends a KNOCK from a PyLink client."""
     # KNOCKs in UnrealIRCd are actually just specially formatted NOTICEs,
     # sent to all ops in a channel.
     # <- :unreal.midnight.vpn NOTICE @#test :[Knock] by GL|!gl@hidden-1C620195 (test)
     assert utils.isChannel(target), "Can only knock on channels!"
     sender = self.irc.getServer(numeric)
     s = '[Knock] by %s (%s)' % (self.irc.getHostmask(numeric), text)
     self._send(sender, 'NOTICE @%s :%s' % (target, s))
コード例 #13
0
ファイル: unreal.py プロジェクト: IotaSpencer/PyLink
    def mode(self, numeric, target, modes, ts=None):
        """
        Sends mode changes from a PyLink client/server. The mode list should be
        a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.
        """
        # <- :unreal.midnight.vpn MODE #test +ntCo GL 1444361345

        if (not self.irc.isInternalClient(numeric)) and \
                (not self.irc.isInternalServer(numeric)):
            raise LookupError('No such PyLink client/server exists.')

        self.irc.applyModes(target, modes)

        if utils.isChannel(target):

            # Make sure we expand any PUIDs when sending outgoing modes...
            for idx, mode in enumerate(modes):
                if mode[0][-1] in self.irc.prefixmodes:
                    log.debug('(%s) mode: expanding PUID of mode %s', self.irc.name, str(mode))
                    modes[idx] = (mode[0], self._expandPUID(mode[1]))

            # The MODE command is used for channel mode changes only
            ts = ts or self.irc.channels[self.irc.toLower(target)].ts

            # 7 characters for "MODE", the space between MODE and the target, the space between the
            # target and mode list, and the space between the mode list and TS.
            bufsize = S2S_BUFSIZE - 7

            # Subtract the length of the TS and channel arguments
            bufsize -= len(str(ts))
            bufsize -= len(target)

            # Subtract the prefix (":SID " for servers or ":SIDAAAAAA " for servers)
            bufsize -= (5 if self.irc.isInternalServer(numeric) else 11)

            # There is also an (undocumented) 15 args per line limit for MODE. The target, mode
            # characters, and TS take up three args, so we're left with 12 spaces for parameters.
            # Any lines that go over 15 args/line has the potential of corrupting a channel's TS
            # pretty badly, as the last argument gets mangled into a number:
            # * *** Warning! Possible desynch: MODE for channel #test ('+bbbbbbbbbbbb *!*@0.1 *!*@1.1 *!*@2.1 *!*@3.1 *!*@4.1 *!*@5.1 *!*@6.1 *!*@7.1 *!*@8.1 *!*@9.1 *!*@10.1 *!*@11.1') has fishy timestamp (12) (from pylink.local/pylink.local)

            # Thanks to kevin and Jobe for helping me debug this!
            for modestring in self.irc.wrapModes(modes, bufsize, max_modes_per_msg=12):
                self._send(numeric, 'MODE %s %s %s' % (target, modestring, ts))
        else:
            # For user modes, the only way to set modes (for non-U:Lined servers)
            # is through UMODE2, which sets the modes on the caller.
            # U:Lines can use SVSMODE/SVS2MODE, but I won't expect people to
            # U:Line a PyLink daemon...
            if not self.irc.isInternalClient(target):
                raise ProtocolError('Cannot force mode change on external clients!')

            # XXX: I don't expect usermode changes to ever get cut off, but length
            # checks could be added just to be safe...
            joinedmodes = self.irc.joinModes(modes)
            self._send(target, 'UMODE2 %s' % joinedmodes)
コード例 #14
0
ファイル: example.py プロジェクト: pombredanne/PyLink
def hook_privmsg(irc, source, command, args):
    channel = args['target']
    text = args['text']

    # irc.pseudoclient stores the IrcUser object of the main PyLink client.
    # (i.e. the user defined in the bot: section of the config)
    if utils.isChannel(channel) and irc.pseudoclient.nick in text:
        irc.msg(channel, 'hi there!')
        # log.debug, log.info, log.warning, log.error, log.exception (within except: clauses)
        # and log.critical are supported here.
        log.info('%s said my name on channel %s (PRIVMSG hook caught)' % (source, channel))
コード例 #15
0
def msg(irc, source, args):
    """[<source>] <target> <text>

    Admin-only. Sends message <text> from <source>, where <source> is the nick of a PyLink client. If <source> is not given, it defaults to the main PyLink client."""
    permissions.checkPermissions(irc, source, ['bots.msg'])

    # Because we want the source nick to be optional, this argument parsing gets a bit tricky.
    try:
        msgsource = args[0]
        target = args[1]
        text = ' '.join(args[2:])

        # First, check if the first argument is an existing PyLink client. If it is not,
        # then assume that the first argument was actually the message TARGET.
        sourceuid = irc.nickToUid(msgsource)
        if not irc.isInternalClient(
                sourceuid):  # First argument isn't one of our clients
            raise IndexError

        if not text:
            raise IndexError
    except IndexError:
        try:
            sourceuid = irc.pseudoclient.uid
            target = args[0]
            text = ' '.join(args[1:])
        except IndexError:
            irc.error(
                'Not enough arguments. Needs 2-3: source nick (optional), target, text.'
            )
            return

    if not text:
        irc.error('No text given.')
        return

    if not utils.isChannel(target):
        # Convert nick of the message target to a UID, if the target isn't a channel
        real_target = irc.nickToUid(target)
        if real_target is None:  # Unknown target user, if target isn't a valid channel name
            irc.error('Unknown user %r.' % target)
            return
    else:
        real_target = target

    irc.proto.message(sourceuid, real_target, text)
    irc.reply("Done.")
    irc.callHooks([
        sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {
            'target': real_target,
            'text': text,
            'parse_as': 'PRIVMSG'
        }
    ])
コード例 #16
0
ファイル: bots.py プロジェクト: pombredanne/PyLink
def part(irc, source, args):
    """[<target>] <channel1>,[<channel2>],... [<reason>]

    Admin-only. Parts <target>, the nick of a PyLink client, from a comma-separated list of channels. If <target> is not given, it defaults to the main PyLink client."""
    irc.checkAuthenticated(source, allowOper=False)

    try:
        nick = args[0]
        clist = args[1]
        # For the part message, join all remaining arguments into one text string
        reason = ' '.join(args[2:])

        # First, check if the first argument is an existing PyLink client. If it is not,
        # then assume that the first argument was actually the channels being parted.
        u = irc.nickToUid(nick)
        if not irc.isInternalClient(
                u):  # First argument isn't one of our clients
            raise IndexError

    except IndexError:  # No nick was given; shift arguments one to the left.
        u = irc.pseudoclient.uid

        try:
            clist = args[0]
        except IndexError:
            irc.reply(
                "Error: Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels."
            )
            return
        reason = ' '.join(args[1:])

    clist = clist.split(',')
    if not clist:
        irc.reply("Error: No valid channels given.")
        return

    if not irc.isManipulatableClient(u):
        irc.reply(
            "Error: Cannot force part a protected PyLink services client.")
        return

    for channel in clist:
        if not utils.isChannel(channel):
            irc.reply("Error: Invalid channel name %r." % channel)
            return
        irc.proto.part(u, channel, reason)

    irc.callHooks([
        u, 'PYLINK_BOTSPLUGIN_PART', {
            'channels': clist,
            'text': reason,
            'parse_as': 'PART'
        }
    ])
コード例 #17
0
ファイル: ts6_common.py プロジェクト: pombredanne/PyLink
    def handle_privmsg(self, source, command, args):
        """Handles incoming PRIVMSG/NOTICE."""
        # <- :70MAAAAAA PRIVMSG #dev :afasfsa
        # <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
        target = args[0]

        # We use lowercase channels internally, but uppercase UIDs.
        stripped_target = target.lstrip(''.join(self.irc.prefixmodes.values()))
        if utils.isChannel(stripped_target):
            target = self.irc.toLower(target)

        return {'target': target, 'text': args[1]}
コード例 #18
0
    def mode(self, numeric, target, modes, ts=None):
        """Sends mode changes from a PyLink client/server."""
        # -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
        # -> :9PYAAAAAA MODE 9PYAAAAAA -i+w

        if (not self.irc.isInternalClient(numeric)) and \
                (not self.irc.isInternalServer(numeric)):
            raise LookupError('No such PyLink client/server exists.')

        log.debug('(%s) inspircd._sendModes: received %r for mode list', self.irc.name, modes)
        if ('+o', None) in modes and not utils.isChannel(target):
            # https://github.com/inspircd/inspircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
            # Servers need a special command to set umode +o on people.
            self._operUp(target)
        self.irc.applyModes(target, modes)
        joinedmodes = self.irc.joinModes(modes)
        if utils.isChannel(target):
            ts = ts or self.irc.channels[self.irc.toLower(target)].ts
            self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
        else:
            self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
コード例 #19
0
def handle_fantasy(irc, source, command, args):
    """Fantasy command handler."""

    if not irc.connected.is_set():
        # Break if the IRC network isn't ready.
        return

    respondtonick = irc.botdata.get("respondtonick")

    channel = args['target']
    orig_text = args['text']

    if utils.isChannel(channel) and not irc.isInternalClient(source):
        # The following conditions must be met for an incoming message for
        # fantasy to trigger:
        #   1) The message target is a channel.
        #   2) A PyLink service client exists in the channel.
        #   3) The message starts with one of our fantasy prefixes.
        #   4) The sender is NOT a PyLink client (this prevents infinite
        #      message loops).
        for botname, sbot in world.services.items():
            log.debug('(%s) fantasy: checking bot %s', irc.name, botname)
            servuid = sbot.uids.get(irc.name)
            if servuid in irc.channels[channel].users:

                # Try to look up a prefix specific for this bot in
                # bot: prefixes: <botname>, falling back to the default prefix if not
                # specified.
                prefixes = [irc.botdata.get('prefixes', {}).get(botname) or
                            irc.botdata.get('prefix')]

                # If responding to nick is enabled, add variations of the current nick
                # to the prefix list: "<nick>," and "<nick>:"
                nick = irc.users[servuid].nick

                if respondtonick:
                    prefixes += [nick+',', nick+':']

                if not any(prefixes):
                    # We finished with an empty prefixes list, meaning fantasy is misconfigured!
                    log.warning("(%s) Fantasy prefix for bot %s was not set in configuration - "
                                "fantasy commands will not work!", irc.name, botname)
                    continue

                for prefix in prefixes:  # Cycle through the prefixes list we finished with.
                     if prefix and orig_text.startswith(prefix):

                        # Cut off the length of the prefix from the text.
                        text = orig_text[len(prefix):]

                        # Finally, call the bot command and loop to the next bot.
                        sbot.call_cmd(irc, source, text, called_in=channel)
                        continue
コード例 #20
0
ファイル: bots.py プロジェクト: pombredanne/PyLink
def joinclient(irc, source, args):
    """[<target>] <channel1>,[<channel2>], etc.

    Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels. If <target> is not given, it defaults to the main PyLink client."""
    irc.checkAuthenticated(source, allowOper=False)

    try:
        # Check if the first argument is an existing PyLink client. If it is not,
        # then assume that the first argument was actually the channels being joined.
        u = irc.nickToUid(args[0])

        if not irc.isInternalClient(
                u):  # First argument isn't one of our clients
            raise IndexError

        clist = args[1]
    except IndexError:  # No nick was given; shift arguments one to the left.
        u = irc.pseudoclient.uid
        try:
            clist = args[0]
        except IndexError:
            irc.reply(
                "Error: Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels."
            )
            return

    clist = clist.split(',')
    if not clist:
        irc.reply("Error: No valid channels given.")
        return

    if not irc.isManipulatableClient(u):
        irc.reply(
            "Error: Cannot force join a protected PyLink services client.")
        return

    for channel in clist:
        if not utils.isChannel(channel):
            irc.reply("Error: Invalid channel name %r." % channel)
            return
        irc.proto.join(u, channel)

        # Call a join hook manually so other plugins like relay can understand it.
        irc.callHooks([
            u, 'PYLINK_BOTSPLUGIN_JOIN', {
                'channel': channel,
                'users': [u],
                'modes': irc.channels[channel].modes,
                'parse_as': 'JOIN'
            }
        ])
コード例 #21
0
def mimic(irc, source, args):
    """<channel> <log glob>

    Echoes chatlogs matching the log glob to the given channel. Home folders ("~") and environment variables ($HOME, etc.) are expanded in THAT order."""
    irc.checkAuthenticated(source, allowOper=False)
    try:
        channel, logs = args[:2]
    except ValueError:
        irc.reply("Error: Not enough arguments. Needs 2: channel, log glob.")
        return
    else:
        assert utils.isChannel(channel), "Invalid channel %s" % channel
        channel = irc.toLower(channel)

        # Expand variables in the path glob
        logs = os.path.expandvars(os.path.expanduser(logs))

    mysid = irc.proto.spawnServer(HOSTNAME)
    logs = sorted(glob.glob(logs))

    def talk():
        userdict = {}
        for item in logs:
            irc.proto.notice(irc.pseudoclient.uid, channel, 'Beginning mimic of log file %s' % item)
            with open(item, errors='replace') as f:  # errors='replace' ignores UnicodeDecodeError
                for line in f.readlines():
                    action = False  # Marks whether line is an action
                    match = CHAT_REGEX.search(line)
                    actionmatch = ACTION_REGEX.search(line)

                    if actionmatch:
                        action = True

                    # Only one of the two matches need to exist for the check below
                    match = match or actionmatch

                    if match:
                        sender, text = match.group(1, 2)
                        # Update the user dict returned by _sayit(), which automatically spawns users
                        # as they're seen.
                        userdict = _sayit(irc, mysid, userdict, channel,
                                          sender, text, action=action)

                # Pause for a bit to prevent excess flood.
                time.sleep(LINEDELAY)
        else:
            # Once we're done, SQUIT everyone to clean up automagically.
            irc.proto.notice(irc.pseudoclient.uid, channel, 'Finished mimic of %s items' % len(logs))
            irc.proto.squit(irc.sid, mysid)

    threading.Thread(target=talk).start()
コード例 #22
0
ファイル: unreal.py プロジェクト: Ro9ueAdmin/PyLink
    def handle_mode(self, numeric, command, args):
        # <- :unreal.midnight.vpn MODE #test +bb test!*@* *!*@bad.net
        # <- :unreal.midnight.vpn MODE #test +q GL 1444361345
        # <- :unreal.midnight.vpn MODE #test +ntCo GL 1444361345
        # <- :unreal.midnight.vpn MODE #test +mntClfo 5 [10t]:5  GL 1444361345
        # <- :GL MODE #services +v GL

        # This seems pretty relatively inconsistent - why do some commands have a TS at the end while others don't?
        # Answer: the first syntax (MODE sent by SERVER) is used for channel bursts - according to Unreal 3.2 docs,
        # the last argument should be interpreted as a timestamp ONLY if it is a number and the sender is a server.
        # Ban bursting does not give any TS, nor do normal users setting modes. SAMODE is special though, it will
        # send 0 as a TS argument (which should be ignored unless breaking the internal channel TS is desired).

        # Also, we need to get rid of that extra space following the +f argument. :|
        if utils.isChannel(args[0]):
            channel = self.irc.toLower(args[0])
            oldobj = self.irc.channels[channel].deepcopy()

            modes = [arg for arg in args[1:] if arg]  # normalize whitespace
            parsedmodes = self.irc.parseModes(channel, modes)

            if parsedmodes:
                if parsedmodes[0][0] == '+&':
                    # UnrealIRCd uses a & virtual mode to denote mode bounces, meaning that an
                    # attempt to set modes by us was rejected for some reason (usually due to
                    # timestamps). Drop the mode change to prevent mode floods.
                    log.debug(
                        "(%s) Received mode bounce %s in channel %s! Our TS: %s",
                        self.irc.name, modes, channel,
                        self.irc.channels[channel].ts)
                    return

                self.irc.applyModes(channel, parsedmodes)

            if numeric in self.irc.servers and args[-1].isdigit():
                # Sender is a server AND last arg is number. Perform TS updates.
                their_ts = int(args[-1])
                if their_ts > 0:
                    self.updateTS(numeric, channel, their_ts)
            return {
                'target': channel,
                'modes': parsedmodes,
                'channeldata': oldobj
            }
        else:
            # User mode change: pass those on to handle_umode2()
            self.handle_umode2(numeric, 'MODE', args[1:])
コード例 #23
0
    def handle_404(self, source, command, args):
        """
        Handles ERR_CANNOTSENDTOCHAN and other similar numerics.
        """
        # <- :some.server 404 james #test :Cannot send to channel
        if len(args) >= 2 and utils.isChannel(args[1]):
            channel = args[1]
            f = log.warning

            # Don't sent the warning multiple times to prevent flood if the target
            # is a log chan.
            if hasattr(self.irc.channels[channel],
                       '_clientbot_cannot_send_warned'):
                f = log.debug
            f('(%s) Failed to send message to %s: %s', self.irc.name, channel,
              args[-1])

            self.irc.channels[channel]._clientbot_cannot_send_warned = True
コード例 #24
0
ファイル: clientbot.py プロジェクト: IotaSpencer/PyLink
    def handle_privmsg(self, source, command, args):
        """Handles incoming PRIVMSG/NOTICE."""
        # <- :sender PRIVMSG #dev :afasfsa
        # <- :sender NOTICE somenick :afasfsa
        target = args[0]

        if self.irc.isInternalClient(source) or self.irc.isInternalServer(
                source):
            log.warning('(%s) Received %s to %s being routed the wrong way!',
                        self.irc.name, command, target)
            return

        # We use lowercase channels internally.
        if utils.isChannel(target):
            target = self.irc.toLower(target)
        else:
            target = self.irc.nickToUid(target)
        if target:
            return {'target': target, 'text': args[1]}
コード例 #25
0
def setacc(irc, source, args):
    """<channel> <mask> <mode list>

    Assigns the given prefix mode characters to the given mask for the channel given. Extended targets are supported for masks - use this to your advantage!

    Examples:
    SET #channel *!*@localhost ohv
    SET #channel $account v
    SET #channel $oper:Network?Administrator qo
    SET #staffchan $channel:#mainchan:op o
    """
    irc.checkAuthenticated(source, allowOper=False)
    try:
        channel, mask, modes = args
    except ValueError:
        reply(
            irc,
            "Error: Invalid arguments given. Needs 3: channel, mask, mode list."
        )
        return
    else:
        if not utils.isChannel(channel):
            reply(irc, "Error: Invalid channel name %s." % channel)
            return

        # Store channels case insensitively
        channel = irc.toLower(channel)

    # Database entries for any network+channel pair are automatically created using
    # defaultdict. Note: string keys are used here instead of tuples so they can be
    # exported easily as JSON.
    dbentry = db[irc.name + channel]

    # Otherwise, update the modes as is.
    dbentry[mask] = modes
    reply(
        irc, "Done. \x02%s\x02 now has modes \x02%s\x02 in \x02%s\x02." %
        (mask, modes, channel))
コード例 #26
0
def joinclient(irc, source, args):
    """[<target>] <channel1>[,<channel2>,<channel3>,...]

    Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels.
    If <target> is not given, it defaults to the main PyLink client.

    For the channel arguments, prefixes can also be specified to join the given client with
    (e.g. @#channel will join the client with op, while ~@#channel will join it with +qo.
    """
    permissions.checkPermissions(irc, source, ['bots.joinclient'])

    try:
        # Check if the first argument is an existing PyLink client. If it is not,
        # then assume that the first argument was actually the channels being joined.
        u = irc.nickToUid(args[0])

        if not irc.isInternalClient(
                u):  # First argument isn't one of our clients
            raise IndexError

        clist = args[1]
    except IndexError:  # No nick was given; shift arguments one to the left.
        u = irc.pseudoclient.uid
        try:
            clist = args[0]
        except IndexError:
            irc.error(
                "Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels."
            )
            return

    clist = clist.split(',')
    if not clist:
        irc.error("No valid channels given.")
        return

    if not (irc.isManipulatableClient(u) or irc.getServiceBot(u)):
        irc.error("Cannot force join a protected PyLink services client.")
        return

    prefix_to_mode = {v: k for k, v in irc.prefixmodes.items()}
    for channel in clist:
        real_channel = channel.lstrip(''.join(prefix_to_mode))
        # XXX we need a better way to do this, but only the other option I can think of is regex...
        prefixes = channel[:len(channel) - len(real_channel)]
        joinmodes = ''.join(prefix_to_mode[prefix] for prefix in prefixes)

        if not utils.isChannel(real_channel):
            irc.error("Invalid channel name %r." % real_channel)
            return

        # join() doesn't support prefixes.
        if prefixes:
            irc.proto.sjoin(irc.sid, real_channel, [(joinmodes, u)])
        else:
            irc.proto.join(u, real_channel)

        # Call a join hook manually so other plugins like relay can understand it.
        irc.callHooks([
            u, 'PYLINK_BOTSPLUGIN_JOIN', {
                'channel': real_channel,
                'users': [u],
                'modes': irc.channels[real_channel].modes,
                'parse_as': 'JOIN'
            }
        ])
    irc.reply("Done.")
コード例 #27
0
ファイル: fantasy.py プロジェクト: Ro9ueAdmin/PyLink
def handle_fantasy(irc, source, command, args):
    """Fantasy command handler."""

    if not irc.connected.is_set():
        # Break if the IRC network isn't ready.
        return

    channel = args['target']
    orig_text = args['text']

    if utils.isChannel(channel) and not irc.isInternalClient(source):
        # The following conditions must be met for an incoming message for
        # fantasy to trigger:
        #   1) The message target is a channel.
        #   2) A PyLink service client exists in the channel.
        #   3) The message starts with one of our fantasy prefixes.
        #   4) The sender is NOT a PyLink client (this prevents infinite
        #      message loops).
        for botname, sbot in world.services.copy().items():
            if botname not in world.services:  # Bot was removed during iteration
                continue

            # Check respond to nick options in this order:
            # 1) The service specific "respond_to_nick" option
            # 2) The global "pylink::respond_to_nick" option
            # 3) The (deprecated) global "bot::respondtonick" option.
            respondtonick = conf.conf.get(botname, {}).get(
                'respond_to_nick',
                conf.conf['pylink'].get("respond_to_nick",
                                        conf.conf['bot'].get("respondtonick")))

            log.debug('(%s) fantasy: checking bot %s', irc.name, botname)
            servuid = sbot.uids.get(irc.name)
            if servuid in irc.channels[channel].users:

                # Look up a string prefix for this bot in either its own configuration block, or
                # in bot::prefixes::<botname>.
                prefixes = [
                    conf.conf.get(botname, {}).get(
                        'prefix', conf.conf['bot'].get('prefixes',
                                                       {}).get(botname))
                ]

                # If responding to nick is enabled, add variations of the current nick
                # to the prefix list: "<nick>," and "<nick>:"
                nick = irc.toLower(irc.users[servuid].nick)

                nick_prefixes = [nick + ',', nick + ':']
                if respondtonick:
                    prefixes += nick_prefixes

                if not any(prefixes):
                    # No prefixes were set, so skip.
                    continue

                lowered_text = irc.toLower(orig_text)
                for prefix in filter(
                        None, prefixes
                ):  # Cycle through the prefixes list we finished with.
                    if lowered_text.startswith(prefix):

                        # Cut off the length of the prefix from the text.
                        text = orig_text[len(prefix):]

                        # HACK: don't trigger on commands like "& help" to prevent false positives.
                        # Weird spacing like "PyLink:   help" and "/msg PyLink   help" should still
                        # work though.
                        if text.startswith(
                                ' ') and prefix not in nick_prefixes:
                            log.debug(
                                '(%s) fantasy: skipping trigger with text prefix followed by space',
                                irc.name)
                            continue

                        # Finally, call the bot command and loop to the next bot.
                        sbot.call_cmd(irc, source, text, called_in=channel)
                        continue
コード例 #28
0
def spawn_service(irc, source, command, args):
    """Handles new service bot introductions."""

    if not irc.connected.is_set():
        return

    # Service name
    name = args['name']

    # Get the ServiceBot object.
    sbot = world.services[name]

    # Look up the nick or ident in the following order:
    # 1) Network specific nick/ident settings for this service (servers::irc.name::servicename_nick)
    # 2) Global settings for this service (servicename::nick)
    # 3) The preferred nick/ident combination defined by the plugin (sbot.nick / sbot.ident)
    # 4) The literal service name.
    # settings, and then falling back to the literal service name.
    nick = irc.serverdata.get("%s_nick" % name) or irc.conf.get(
        name, {}).get('nick') or sbot.nick or name
    ident = irc.serverdata.get("%s_ident" % name) or irc.conf.get(
        name, {}).get('ident') or sbot.ident or name

    # TODO: make this configurable?
    host = irc.serverdata["hostname"]

    # Spawning service clients with these umodes where supported. servprotect usage is a
    # configuration option.
    preferred_modes = ['oper', 'hideoper', 'hidechans', 'invisible', 'bot']
    modes = []

    if conf.conf['bot'].get('protect_services'):
        preferred_modes.append('servprotect')

    for mode in preferred_modes:
        mode = irc.umodes.get(mode)
        if mode:
            modes.append((mode, None))

    # Track the service's UIDs on each network.
    userobj = irc.proto.spawnClient(nick,
                                    ident,
                                    host,
                                    modes=modes,
                                    opertype="PyLink Service",
                                    manipulatable=sbot.manipulatable)

    sbot.uids[irc.name] = u = userobj.uid

    # Special case: if this is the main PyLink client being spawned,
    # assign this as irc.pseudoclient.
    if name == 'pylink':
        irc.pseudoclient = userobj

    # TODO: channels should be tracked in a central database, not hardcoded
    # in conf.
    channels = set(irc.serverdata.get(
        'channels', [])) | sbot.extra_channels.get(irc.name, set())

    for chan in channels:
        if utils.isChannel(chan):
            irc.proto.join(u, chan)
            irc.callHooks([
                irc.sid, 'PYLINK_SERVICE_JOIN', {
                    'channel': chan,
                    'users': [u]
                }
            ])
        else:
            log.warning('(%s) Ignoring invalid autojoin channel %r.', irc.name,
                        chan)