diff --git a/classes.py b/classes.py index c5b1abe..c2a8c9f 100644 --- a/classes.py +++ b/classes.py @@ -1157,12 +1157,18 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore): possible_modes['*A'] += ''.join(self.prefixmodes) for name, userlist in c.prefixmodes.items(): try: - oldmodes.update([(self.cmodes[name], u) for u in userlist]) + # Add prefix modes to the list of old modes + oldmodes |= {(self.cmodes[name], u) for u in userlist} except KeyError: continue else: - oldmodes = self.users[target].modes + oldmodes = set(self.users[target].modes) possible_modes = self.umodes + + oldmodes_mapping = dict(oldmodes) + oldmodes_lower = {(modepair[0], self.to_lower(modepair[1]) if modepair[1] else modepair[1]) + for modepair in oldmodes} + newmodes = [] log.debug('(%s) reverse_modes: old/current mode list for %s is: %s', self.name, target, oldmodes) @@ -1177,24 +1183,29 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore): # We need to look at the current mode list to reset modes that take arguments # For example, trying to bounce +l 30 on a channel that had +l 50 set should # give "+l 50" and not "-l". - oldarg = [m for m in oldmodes if m[0] == mchar] + oldarg = oldmodes_mapping.get(mchar) + if oldarg: # Old mode argument for this mode existed, use that. - oldarg = oldarg[0] - mpair = ('+%s' % oldarg[0], oldarg[1]) + mpair = ('+%s' % mchar, oldarg) + 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 arg is not None: + arg = self.to_lower(arg) if char[0] != '-' and (mchar, arg) in oldmodes: # Mode is already set. log.debug("(%s) reverse_modes: 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 prefix mode that was never set - don't set it in response! + # We're unsetting a list or prefix mode that was never set - don't set it in response! # TS6 IRCds lacks server-side verification for this and can cause annoying mode floods. log.debug("(%s) reverse_modes: skipping reversing '%s %s' with %s since it " "wasn't previously set.", self.name, char, arg, mpair) diff --git a/test/protocol_test_fixture.py b/test/protocol_test_fixture.py index d790fec..ec6d7c7 100644 --- a/test/protocol_test_fixture.py +++ b/test/protocol_test_fixture.py @@ -681,11 +681,10 @@ class BaseProtocolTest(unittest.TestCase): c.modes = {('t', None), ('n', None), ('l', '50')} out = self.p.reverse_modes('#foobar', [('+l', '100')]) - self.assertEqual(out, [('+l', '50')], "Setting +l should reset original mode") out = self.p.reverse_modes('#foobar', [('-l', None)]) - self.assertEqual(out, [('+l', '50')], "Setting +l should reset original mode") + self.assertEqual(out, [('+l', '50')], "Unsetting +l should reset original mode") out = self.p.reverse_modes('#foobar', [('+l', '50')]) self.assertEqual(out, [], "Setting +l with original value is no-op") @@ -700,7 +699,7 @@ class BaseProtocolTest(unittest.TestCase): u = self._make_user('nick', uid='user') u.channels.add(c) c.users.add(u) - c.prefixmodes['op'].add(u) + c.prefixmodes['op'].add(u.uid) out = self.p.reverse_modes('#foobar', '-o user') self.assertEqual(out, '+o user')