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