From 064cb9b6aa88dcacde876c35968f0ce9912e1084 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 30 Apr 2016 16:33:46 -0700 Subject: [PATCH] core: move reverseModes, joinModes into Irc --- classes.py | 128 +++++++++++++++++++++++++++++++++++++++++ coreplugin.py | 2 +- plugins/commands.py | 4 +- plugins/relay.py | 2 +- protocols/hybrid.py | 2 +- protocols/inspircd.py | 8 +-- protocols/nefarious.py | 6 +- protocols/ts6.py | 8 +-- protocols/unreal.py | 4 +- utils.py | 126 ---------------------------------------- 10 files changed, 146 insertions(+), 144 deletions(-) diff --git a/classes.py b/classes.py index 2d5816b..1a3ddc5 100644 --- a/classes.py +++ b/classes.py @@ -653,6 +653,134 @@ class Irc(): else: self.channels[target].modes = modelist + @staticmethod + def _flip(mode): + """Flips a mode character.""" + # Make it a list first, strings don't support item assignment + mode = list(mode) + if mode[0] == '-': # Query is something like "-n" + mode[0] = '+' # Change it to "+n" + elif mode[0] == '+': + mode[0] = '-' + else: # No prefix given, assume + + mode.insert(0, '-') + return ''.join(mode) + + def reverseModes(self, target, modes, oldobj=None): + """Reverses/Inverts the mode string or mode list given. + + Optionally, an oldobj argument can be given to look at an earlier state of + a channel/user object, e.g. for checking the op status of a mode setter + before their modes are processed and added to the channel state. + + This function allows both mode strings or mode lists. Example uses: + "+mi-lk test => "-mi+lk test" + "mi-k test => "-mi+k test" + [('+m', None), ('+r', None), ('+l', '3'), ('-o', 'person') + => {('-m', None), ('-r', None), ('-l', None), ('+o', 'person')}) + {('s', None), ('+o', 'whoever') => {('-s', None), ('-o', 'whoever')}) + """ + origtype = type(modes) + # If the query is a string, we have to parse it first. + if origtype == str: + modes = self.parseModes(target, modes.split(" ")) + # Get the current mode list first. + if utils.isChannel(target): + c = oldobj or self.channels[target] + oldmodes = c.modes.copy() + possible_modes = self.cmodes.copy() + # For channels, this also includes the list of prefix modes. + possible_modes['*A'] += ''.join(self.prefixmodes) + for name, userlist in c.prefixmodes.items(): + try: + oldmodes.update([(self.cmodes[name], u) for u in userlist]) + except KeyError: + continue + else: + oldmodes = self.users[target].modes + possible_modes = self.umodes + newmodes = [] + log.debug('(%s) reverseModes: old/current mode list for %s is: %s', self.name, + target, oldmodes) + for char, arg in modes: + # Mode types: + # A = Mode that adds or removes a nick or address to a list. Always has a parameter. + # B = Mode that changes a setting and always has a parameter. + # C = Mode that changes a setting and only has a parameter when set. + # D = Mode that changes a setting and never has a parameter. + mchar = char[-1] + if mchar in possible_modes['*B'] + possible_modes['*C']: + # We need to find the current mode list, so we can reset arguments + # for modes that have arguments. For example, setting +l 30 on a channel + # that had +l 50 set should give "+l 30", not "-l". + oldarg = [m for m in oldmodes if m[0] == mchar] + if oldarg: # Old mode argument for this mode existed, use that. + oldarg = oldarg[0] + mpair = ('+%s' % oldarg[0], oldarg[1]) + else: # Not found, flip the mode then. + # Mode takes no arguments when unsetting. + if mchar in possible_modes['*C'] and char[0] != '-': + arg = None + mpair = (self._flip(char), arg) + else: + mpair = (self._flip(char), arg) + if char[0] != '-' and (mchar, arg) in oldmodes: + # Mode is already set. + log.debug("(%s) reverseModes: skipping reversing '%s %s' with %s since we're " + "setting a mode that's already set.", self.name, char, arg, mpair) + continue + elif char[0] == '-' and (mchar, arg) not in oldmodes and mchar in possible_modes['*A']: + # We're unsetting a prefixmode that was never set - don't set it in response! + # Charybdis lacks verification for this server-side. + log.debug("(%s) reverseModes: skipping reversing '%s %s' with %s since it " + "wasn't previously set.", self.name, char, arg, mpair) + continue + newmodes.append(mpair) + + log.debug('(%s) reverseModes: new modes: %s', self.name, newmodes) + if origtype == str: + # If the original query is a string, send it back as a string. + return self.joinModes(newmodes) + else: + return set(newmodes) + + @staticmethod + def joinModes(modes): + """Takes a list of (mode, arg) tuples in parseModes() format, and + joins them into a string. + + See testJoinModes in tests/test_utils.py for some examples.""" + prefix = '+' # Assume we're adding modes unless told otherwise + modelist = '' + args = [] + for modepair in modes: + mode, arg = modepair + assert len(mode) in (1, 2), "Incorrect length of a mode (received %r)" % mode + try: + # If the mode has a prefix, use that. + curr_prefix, mode = mode + except ValueError: + # If not, the current prefix stays the same; move on to the next + # modepair. + pass + else: + # If the prefix of this mode isn't the same as the last one, add + # the prefix to the modestring. This prevents '+nt-lk' from turning + # into '+n+t-l-k' or '+ntlk'. + if prefix != curr_prefix: + modelist += curr_prefix + prefix = curr_prefix + modelist += mode + if arg is not None: + args.append(arg) + if not modelist.startswith(('+', '-')): + # Our starting mode didn't have a prefix with it. Assume '+'. + modelist = '+' + modelist + if args: + # Add the args if there are any. + modelist += ' %s' % ' '.join(args) + return modelist + ### State checking functions def nickToUid(self, nick): """Looks up the UID of a user with the given nick, if one is present.""" diff --git a/coreplugin.py b/coreplugin.py index 3ee3850..e546075 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -121,7 +121,7 @@ def handle_whois(irc, source, command, args): # Only show this to opers! if sourceisOper: f(server, 378, source, "%s :is connecting from %s@%s %s" % (nick, user.ident, user.realhost, user.ip)) - f(server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes))) + f(server, 379, source, '%s :is using modes %s' % (nick, irc.joinModes(user.modes))) # 301: used to show away information if present away_text = user.away diff --git a/plugins/commands.py b/plugins/commands.py index dfcc485..6e99df5 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -101,7 +101,7 @@ def showuser(irc, source, args): if verbose: # Oper only data: user modes, channels on, account info, etc. - f('\x02User modes\x02: %s' % utils.joinModes(userobj.modes)) + f('\x02User modes\x02: %s' % irc.joinModes(userobj.modes)) f('\x02Protocol UID\x02: %s; \x02Real host\x02: %s; \x02IP\x02: %s' % \ (u, userobj.realhost, userobj.ip)) channels = sorted(userobj.channels) @@ -141,7 +141,7 @@ def showchan(irc, source, args): f('\x02Channel topic\x02: %s' % c.topic) f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts)) # Show only modes that aren't list-style modes. - modes = utils.joinModes([m for m in c.modes if m[0] not in irc.cmodes['*A']]) + modes = irc.joinModes([m for m in c.modes if m[0] not in irc.cmodes['*A']]) f('\x02Channel modes\x02: %s' % modes) if verbose: nicklist = [] diff --git a/plugins/relay.py b/plugins/relay.py index 0acdf9a..15351fc 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -1022,7 +1022,7 @@ def handle_mode(irc, numeric, command, args): if checkClaim(irc, target, numeric, chanobj=oldchan): relayModes(irc, remoteirc, numeric, target, modes) else: # Mode change blocked by CLAIM. - reversed_modes = utils.reverseModes(irc, target, modes, oldobj=oldchan) + reversed_modes = irc.reverseModes(target, modes, oldobj=oldchan) log.debug('(%s) relay.handle_mode: Reversing mode changes of %r with %r (CLAIM).', irc.name, modes, reversed_modes) irc.proto.mode(irc.pseudoclient.uid, target, reversed_modes) diff --git a/protocols/hybrid.py b/protocols/hybrid.py index 57f0877..082a32c 100644 --- a/protocols/hybrid.py +++ b/protocols/hybrid.py @@ -108,7 +108,7 @@ class HybridProtocol(TS6Protocol): ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host - raw_modes = utils.joinModes(modes) + raw_modes = self.irc.joinModes(modes) u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, realhost=realhost, ip=ip, manipulatable=manipulatable) self.irc.applyModes(uid, modes) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 3a028c6..9fa4a0a 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -55,7 +55,7 @@ class InspIRCdProtocol(TS6BaseProtocol): ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host - raw_modes = utils.joinModes(modes) + raw_modes = self.irc.joinModes(modes) u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype) @@ -85,7 +85,7 @@ class InspIRCdProtocol(TS6BaseProtocol): modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']] self._send(server, "FJOIN {channel} {ts} {modes} :,{uid}".format( ts=self.irc.channels[channel].ts, uid=client, channel=channel, - modes=utils.joinModes(modes))) + modes=self.irc.joinModes(modes))) self.irc.channels[channel].users.add(client) self.irc.users[client].channels.add(channel) @@ -136,7 +136,7 @@ class InspIRCdProtocol(TS6BaseProtocol): namelist = ' '.join(namelist) self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format( ts=ts, users=namelist, channel=channel, - modes=utils.joinModes(modes))) + modes=self.irc.joinModes(modes))) self.irc.channels[channel].users.update(uids) def _operUp(self, target, opertype=None): @@ -178,7 +178,7 @@ class InspIRCdProtocol(TS6BaseProtocol): # Servers need a special command to set umode +o on people. self._operUp(target) self.irc.applyModes(target, modes) - joinedmodes = utils.joinModes(modes) + joinedmodes = self.irc.joinModes(modes) if utils.isChannel(target): ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes)) diff --git a/protocols/nefarious.py b/protocols/nefarious.py index 214ef90..be83ecf 100644 --- a/protocols/nefarious.py +++ b/protocols/nefarious.py @@ -262,7 +262,7 @@ class P10Protocol(Protocol): ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host - raw_modes = utils.joinModes(modes) + raw_modes = self.irc.joinModes(modes) # Initialize an IrcUser instance u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, @@ -393,7 +393,7 @@ class P10Protocol(Protocol): send_ts = False while modes[:12]: - joinedmodes = utils.joinModes([m for m in modes[:12]]) + joinedmodes = self.irc.joinModes([m for m in modes[:12]]) modes = modes[12:] self._send(numeric, 'M %s %s%s' % (target, joinedmodes, ' %s' % ts if send_ts else '')) @@ -526,7 +526,7 @@ class P10Protocol(Protocol): if modes: # Only send modes if there are any. self._send(server, "B {channel} {ts} {modes} :{users}".format( ts=ts, users=namelist, channel=channel, - modes=utils.joinModes(modes))) + modes=self.irc.joinModes(modes))) else: self._send(server, "B {channel} {ts} :{users}".format( ts=ts, users=namelist, channel=channel)) diff --git a/protocols/ts6.py b/protocols/ts6.py index baed531..fbe5cb0 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -50,7 +50,7 @@ class TS6Protocol(TS6BaseProtocol): ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host - raw_modes = utils.joinModes(modes) + raw_modes = self.irc.joinModes(modes) u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype) @@ -135,7 +135,7 @@ class TS6Protocol(TS6BaseProtocol): namelist = ' '.join(namelist) self._send(server, "SJOIN {ts} {channel} {modes} :{users}".format( ts=ts, users=namelist, channel=channel, - modes=utils.joinModes(modes))) + modes=self.irc.joinModes(modes))) self.irc.channels[channel].users.update(uids) if ts <= orig_ts: # Only save our prefix modes in the channel state if our TS is lower than or equal to theirs. @@ -163,11 +163,11 @@ class TS6Protocol(TS6BaseProtocol): while modes[:9]: # Seriously, though. If you send more than 10 mode parameters in # a line, charybdis will silently REJECT the entire command! - joinedmodes = utils.joinModes(modes = [m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']]) + joinedmodes = self.irc.joinModes(modes = [m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']]) modes = modes[9:] self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes)) else: - joinedmodes = utils.joinModes(modes) + joinedmodes = self.irc.joinModes(modes) self._send(numeric, 'MODE %s %s' % (target, joinedmodes)) def kill(self, numeric, target, reason): diff --git a/protocols/unreal.py b/protocols/unreal.py index 00c0cfe..303f6bf 100644 --- a/protocols/unreal.py +++ b/protocols/unreal.py @@ -80,7 +80,7 @@ class UnrealProtocol(TS6BaseProtocol): ts = ts or int(time.time()) realname = realname or self.irc.botdata['realname'] realhost = realhost or host - raw_modes = utils.joinModes(modes) + raw_modes = self.irc.joinModes(modes) u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype) self.irc.applyModes(uid, modes) @@ -216,7 +216,7 @@ class UnrealProtocol(TS6BaseProtocol): raise LookupError('No such PyLink client/server exists.') self.irc.applyModes(target, modes) - joinedmodes = utils.joinModes(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[utils.toLower(self.irc, target)].ts diff --git a/utils.py b/utils.py index 748fc62..7074c13 100644 --- a/utils.py +++ b/utils.py @@ -132,132 +132,6 @@ def applyModes(irc, target, changedmodes): log.warning("(%s) utils.applyModes is deprecated. Use irc.applyModes() instead!", irc.name) return irc.applyModes(target, changedmodes) -def joinModes(modes): - """Takes a list of (mode, arg) tuples in parseModes() format, and - joins them into a string. - - See testJoinModes in tests/test_utils.py for some examples.""" - prefix = '+' # Assume we're adding modes unless told otherwise - modelist = '' - args = [] - for modepair in modes: - mode, arg = modepair - assert len(mode) in (1, 2), "Incorrect length of a mode (received %r)" % mode - try: - # If the mode has a prefix, use that. - curr_prefix, mode = mode - except ValueError: - # If not, the current prefix stays the same; move on to the next - # modepair. - pass - else: - # If the prefix of this mode isn't the same as the last one, add - # the prefix to the modestring. This prevents '+nt-lk' from turning - # into '+n+t-l-k' or '+ntlk'. - if prefix != curr_prefix: - modelist += curr_prefix - prefix = curr_prefix - modelist += mode - if arg is not None: - args.append(arg) - if not modelist.startswith(('+', '-')): - # Our starting mode didn't have a prefix with it. Assume '+'. - modelist = '+' + modelist - if args: - # Add the args if there are any. - modelist += ' %s' % ' '.join(args) - return modelist - -def _flip(mode): - """Flips a mode character.""" - # Make it a list first, strings don't support item assignment - mode = list(mode) - if mode[0] == '-': # Query is something like "-n" - mode[0] = '+' # Change it to "+n" - elif mode[0] == '+': - mode[0] = '-' - else: # No prefix given, assume + - mode.insert(0, '-') - return ''.join(mode) - -def reverseModes(irc, target, modes, oldobj=None): - """Reverses/Inverts the mode string or mode list given. - - Optionally, an oldobj argument can be given to look at an earlier state of - a channel/user object, e.g. for checking the op status of a mode setter - before their modes are processed and added to the channel state. - - This function allows both mode strings or mode lists. Example uses: - "+mi-lk test => "-mi+lk test" - "mi-k test => "-mi+k test" - [('+m', None), ('+r', None), ('+l', '3'), ('-o', 'person') - => {('-m', None), ('-r', None), ('-l', None), ('+o', 'person')}) - {('s', None), ('+o', 'whoever') => {('-s', None), ('-o', 'whoever')}) - """ - origtype = type(modes) - # If the query is a string, we have to parse it first. - if origtype == str: - modes = parseModes(irc, target, modes.split(" ")) - # Get the current mode list first. - if isChannel(target): - c = oldobj or irc.channels[target] - oldmodes = c.modes.copy() - possible_modes = irc.cmodes.copy() - # For channels, this also includes the list of prefix modes. - possible_modes['*A'] += ''.join(irc.prefixmodes) - for name, userlist in c.prefixmodes.items(): - try: - oldmodes.update([(irc.cmodes[name], u) for u in userlist]) - except KeyError: - continue - else: - oldmodes = irc.users[target].modes - possible_modes = irc.umodes - newmodes = [] - log.debug('(%s) reverseModes: old/current mode list for %s is: %s', irc.name, - target, oldmodes) - for char, arg in modes: - # Mode types: - # A = Mode that adds or removes a nick or address to a list. Always has a parameter. - # B = Mode that changes a setting and always has a parameter. - # C = Mode that changes a setting and only has a parameter when set. - # D = Mode that changes a setting and never has a parameter. - mchar = char[-1] - if mchar in possible_modes['*B'] + possible_modes['*C']: - # We need to find the current mode list, so we can reset arguments - # for modes that have arguments. For example, setting +l 30 on a channel - # that had +l 50 set should give "+l 30", not "-l". - oldarg = [m for m in oldmodes if m[0] == mchar] - if oldarg: # Old mode argument for this mode existed, use that. - oldarg = oldarg[0] - mpair = ('+%s' % oldarg[0], oldarg[1]) - else: # Not found, flip the mode then. - # Mode takes no arguments when unsetting. - if mchar in possible_modes['*C'] and char[0] != '-': - arg = None - mpair = (_flip(char), arg) - else: - mpair = (_flip(char), arg) - if char[0] != '-' and (mchar, arg) in oldmodes: - # Mode is already set. - log.debug("(%s) reverseModes: skipping reversing '%s %s' with %s since we're " - "setting a mode that's already set.", irc.name, char, arg, mpair) - continue - elif char[0] == '-' and (mchar, arg) not in oldmodes and mchar in possible_modes['*A']: - # We're unsetting a prefixmode that was never set - don't set it in response! - # Charybdis lacks verification for this server-side. - log.debug("(%s) reverseModes: skipping reversing '%s %s' with %s since it " - "wasn't previously set.", irc.name, char, arg, mpair) - continue - newmodes.append(mpair) - - log.debug('(%s) reverseModes: new modes: %s', irc.name, newmodes) - if origtype == str: - # If the original query is a string, send it back as a string. - return joinModes(newmodes) - else: - return set(newmodes) - def isOper(irc, uid, allowAuthed=True, allowOper=True): """ Returns whether the given user has operator status on PyLink. This can be achieved