예제 #1
0
    def sjoin(self, server, channel, users, ts=None, modes=set()):
        """Sends an SJOIN for a group of users to a channel.

        The sender should always be a Server ID (SID). TS is optional, and defaults
        to the one we've stored in the channel state if not given.
        <users> is a list of (prefix mode, UID) pairs:

        Example uses:
            sjoin('100', '#test', [('', 'user0@0'), ('o', user1@1'), ('v', 'someone@2')])
            sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
        """

        server = server or self.sid
        if not server:
            raise LookupError('No such PyLink client exists.')
        log.debug('(%s) sjoin: got %r for users', self.name, users)

        njoin_prefix = ':%s NJOIN %s :' % (self._expandPUID(server), channel)
        # Format the user list into strings such as @user1, +user2, user3, etc.
        nicks_to_send = []
        for userpair in users:
            prefixes, uid = userpair

            if uid not in self.users:
                log.warning('(%s) Trying to NJOIN missing user %s?', self.name,
                            uid)
                continue
            elif uid in self._channels[channel].users:
                # Don't rejoin users already in the channel, this causes errors with ngIRCd.
                continue

            self._channels[channel].users.add(uid)
            self.users[uid].channels.add(channel)

            self.apply_modes(channel,
                             (('+%s' % prefix, uid) for prefix in userpair[0]))

            nicks_to_send.append(''.join(self.prefixmodes[modechar] for modechar in userpair[0]) + \
                                 self._expandPUID(userpair[1]))

        if nicks_to_send:
            # Use 13 args max per line: this is equal to the max of 15 minus the command name and target channel.
            for message in utils.wrap_arguments(njoin_prefix,
                                                nicks_to_send,
                                                self.S2S_BUFSIZE,
                                                separator=',',
                                                max_args_per_line=13):
                self.send(message)

        if modes:
            # Burst modes separately if there are any.
            log.debug("(%s) sjoin: bursting modes %r for channel %r now",
                      self.name, modes, channel)
            self.mode(server, channel, modes)
예제 #2
0
    def sjoin(self, server, channel, users, ts=None, modes=set()):
        """Sends an SJOIN for a group of users to a channel.

        The sender should always be a server (SID). TS is optional, and defaults
        to the one we've stored in the channel state if not given.
        <users> is a list of (prefix mode, UID) pairs:

        Example uses:
            sjoin('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
            sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
        """
        # <- :001 SJOIN 1444361345 #test :*@+1JJAAAAAB %2JJAAAA4C 1JJAAAADS
        server = server or self.sid
        assert users, "sjoin: No users sent?"
        if not server:
            raise LookupError('No such PyLink server exists.')

        changedmodes = set(modes or self._channels[channel].modes)
        orig_ts = self._channels[channel].ts
        ts = ts or orig_ts
        uids = []
        itemlist = []

        for userpair in users:
            assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair
            prefixes, user = userpair

            # Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~,
            # and +a is ~ instead of &.
            # &, ", and ' are used for bursting bans.
            prefixchars = ''.join([SJOIN_PREFIXES.get(prefix, '') for prefix in prefixes])

            if prefixchars:
                changedmodes |= {('+%s' % prefix, user) for prefix in prefixes}

            itemlist.append(prefixchars+user)
            uids.append(user)

            try:
                self.users[user].channels.add(channel)
            except KeyError:  # Not initialized yet?
                log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.name, channel, user)

        # Track simple modes separately.
        simplemodes = set()
        for modepair in modes:
            if modepair[0][-1] in self.cmodes['*A']:
                # Bans, exempts, invex get expanded to forms like "&*!*@some.host" in SJOIN.

                if (modepair[0][-1], modepair[1]) in self._channels[channel].modes:
                    # Mode is already set; skip it.
                    continue

                sjoin_prefix = SJOIN_PREFIXES.get(modepair[0][-1])
                if sjoin_prefix:
                    itemlist.append(sjoin_prefix+modepair[1])
            else:
                simplemodes.add(modepair)

        # Store the part of the SJOIN that we may reuse due to line wrapping (i.e. the sjoin
        # "prefix")
        sjoin_prefix = ":{sid} SJOIN {ts} {channel}".format(sid=server, ts=ts, channel=channel)

        # Modes are optional; add them if they exist
        if modes:
            sjoin_prefix += " %s" % self.join_modes(simplemodes)

        sjoin_prefix += " :"
        # Wrap arguments to the max supported S2S line length to prevent cutoff
        # (https://github.com/jlu5/PyLink/issues/378)
        for line in utils.wrap_arguments(sjoin_prefix, itemlist, self.S2S_BUFSIZE):
            self.send(line)

        self._channels[channel].users.update(uids)

        self.updateTS(server, channel, ts, changedmodes)
예제 #3
0
    def sjoin(self, server, channel, users, ts=None, modes=set()):
        """Sends an SJOIN for a group of users to a channel.

        The sender should always be a Server ID (SID). TS is optional, and defaults
        to the one we've stored in the channel state if not given.
        <users> is a list of (prefix mode, UID) pairs:

        Example uses:
            sjoin('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
            sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
        """
        # https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L821
        # parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist

        # Broadcasts a channel creation or bursts a channel.

        # The nicklist consists of users joining the channel, with status prefixes for
        # their status ('@+', '@', '+' or ''), for example:
        # '@+1JJAAAAAB +2JJAAAA4C 1JJAAAADS'. All users must be behind the source server
        # so it is not possible to use this message to force users to join a channel.
        server = server or self.sid
        assert users, "sjoin: No users sent?"
        log.debug('(%s) sjoin: got %r for users', self.name, users)
        if not server:
            raise LookupError('No such PyLink client exists.')

        modes = set(modes or self._channels[channel].modes)
        orig_ts = self._channels[channel].ts
        ts = ts or orig_ts

        # Get all the ban modes in a separate list. These are bursted using a separate BMASK
        # command.
        banmodes = {k: [] for k in self.cmodes['*A']}
        regularmodes = []
        log.debug('(%s) Unfiltered SJOIN modes: %s', self.name, modes)
        for mode in modes:
            modechar = mode[0][-1]
            if modechar in self.cmodes['*A']:
                # Mode character is one of 'beIq'
                if (modechar, mode[1]) in self._channels[channel].modes:
                    # Don't reset modes that are already set.
                    continue

                banmodes[modechar].append(mode[1])
            else:
                regularmodes.append(mode)
        log.debug(
            '(%s) Filtered SJOIN modes to be regular modes: %s, banmodes: %s',
            self.name, regularmodes, banmodes)

        changedmodes = modes
        while users[:12]:
            uids = []
            namelist = []
            # We take <users> as a list of (prefixmodes, uid) pairs.
            for userpair in users[:12]:
                assert len(
                    userpair
                ) == 2, "Incorrect format of userpair: %r" % userpair
                prefixes, user = userpair
                prefixchars = ''
                for prefix in prefixes:
                    pr = self.prefixmodes.get(prefix)
                    if pr:
                        prefixchars += pr
                        changedmodes.add(('+%s' % prefix, user))
                namelist.append(prefixchars + user)
                uids.append(user)
                try:
                    self.users[user].channels.add(channel)
                except KeyError:  # Not initialized yet?
                    log.debug(
                        "(%s) sjoin: KeyError trying to add %r to %r's channel list?",
                        self.name, channel, user)
            users = users[12:]
            namelist = ' '.join(namelist)
            self._send_with_prefix(
                server, "SJOIN {ts} {channel} {modes} :{users}".format(
                    ts=ts,
                    users=namelist,
                    channel=channel,
                    modes=self.join_modes(regularmodes)))
            self._channels[channel].users.update(uids)

        # Now, burst bans.
        # <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
        for bmode, bans in banmodes.items():
            # Max 15-3 = 12 bans per line to prevent cut off. (TS6 allows a max of 15 parameters per
            # line)
            if bans:
                log.debug('(%s) sjoin: bursting mode %s with bans %s, ts:%s',
                          self.name, bmode, bans, ts)
                msgprefix = ':{sid} BMASK {ts} {channel} {bmode} :'.format(
                    sid=server, ts=ts, channel=channel, bmode=bmode)
                # Actually, we cut off at 17 arguments/line, since the prefix and command name don't count.
                for msg in utils.wrap_arguments(msgprefix,
                                                bans,
                                                self.S2S_BUFSIZE,
                                                max_args_per_line=17):
                    self.send(msg)

        self.updateTS(server, channel, ts, changedmodes)