diff --git a/tests/test_utils.py b/tests/test_utils.py index 94a0194..d2d34f2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -101,19 +101,43 @@ class TestUtils(unittest.TestCase): ('+b', '*!*@*.badisp.net')]) self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net') - @unittest.skip('Wait, we need to work out the kinks first! (reversing changes of modes with arguments)') + def _reverseModes(self, query, expected, target='#test'): + res = utils.reverseModes(self.irc, target, query) + self.assertEqual(res, expected) + def testReverseModes(self): - f = lambda x: utils.reverseModes(self.irc, '#test', x) + test = lambda x, y: self.assertEqual(utils.reverseModes(self.irc, '#test', x), y) # Strings. - self.assertEqual(f("+nt-lk"), "-nt+lk") - self.assertEqual(f("nt-k"), "-nt+k") + self._reverseModes("+mi-lk test", "-mi+lk test") + self._reverseModes("mi-k test", "-mi+k test") # Lists. - self.assertEqual(f([('+m', None), ('+t', None), ('+l', '3'), ('-o', 'person')]), - [('-m', None), ('-t', None), ('-l', '3'), ('+o', 'person')]) + self._reverseModes([('+m', None), ('+r', None), ('+l', '3'), ('-o', 'person')], + {('-m', None), ('-r', None), ('-l', None), ('+o', 'person')}) # Sets. - self.assertEqual(f({('s', None), ('+o', 'whoever')}), {('-s', None), ('-o', 'whoever')}) + self._reverseModes({('s', None), ('+o', 'whoever')}, {('-s', None), ('-o', 'whoever')}) # Combining modes with an initial + and those without - self.assertEqual(f({('s', None), ('+n', None)}), {('-s', None), ('-n', None)}) + self._reverseModes({('s', None), ('+R', None)}, {('-s', None), ('-R', None)}) + + def testReverseModesUser(self): + self._reverseModes({('+i', None), ('l', 'asfasd')}, {('-i', None), ('-l', 'asfasd')}, + target=self.irc.pseudoclient.uid) + + def testReverseModesExisting(self): + utils.applyModes(self.irc, '#test', [('+m', None), ('+l', '50'), ('+k', 'supersecret'), + ('+o', '9PYAAAAAA')]) + + self._reverseModes({('+i', None), ('+l', '3')}, {('-i', None), ('+l', '50')}) + self._reverseModes('-n', '+n') + self._reverseModes('-l', '+l 50') + self._reverseModes('+k derp', '+k supersecret') + self._reverseModes('-mk *', '+mk supersecret') + + # Existing modes are ignored. + self._reverseModes([('+t', None)], set()) + self._reverseModes('+n', '+') + self._reverseModes('+oo GLolol 9PYAAAAAA', '-o GLolol') + self._reverseModes('+o 9PYAAAAAA', '+') + self._reverseModes('+vvvvM test abcde atat abcd', '-vvvvM test abcde atat abcd') if __name__ == '__main__': unittest.main() diff --git a/utils.py b/utils.py index 6ea1359..39c8805 100644 --- a/utils.py +++ b/utils.py @@ -326,10 +326,20 @@ def joinModes(modes): modelist += ' %s' % ' '.join(args) return modelist -def reverseModes(irc, target, modes): - """ +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) - Reverses/Inverts the mode string or mode list given. +def reverseModes(irc, target, modes): + """Reverses/Inverts the mode string or mode list given. "+nt-lk" => "-nt+lk" "nt-k" => "-nt+k" @@ -338,21 +348,56 @@ def reverseModes(irc, target, modes): [('s', None), ('+n', None)] => [('-s', None), ('-n', None)] """ origtype = type(modes) - # Operate on joined modestrings only; it's easier. - if origtype != str: - modes = joinModes(modes) - # Swap the +'s and -'s by replacing one with a dummy character, and then changing it back. - assert '\x00' not in modes, 'NUL cannot be in the mode list (it is a reserved character)!' - if not modes.startswith(('+', '-')): - modes = '+' + modes - newmodes = modes.replace('+', '\x00') - newmodes = newmodes.replace('-', '+') - newmodes = newmodes.replace('\x00', '-') - if origtype != str: - # If the original query isn't a string, send back the parseModes() output. - return parseModes(irc, target, newmodes.split(" ")) + # 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): + oldmodes = irc.channels[target].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 irc.channels[target].prefixmodes.items(): + try: + oldmodes.update([(irc.cmodes[name[:-1]], u) for u in userlist]) + except KeyError: + continue else: - return newmodes + oldmodes = irc.users[target].modes + possible_modes = irc.umodes + newmodes = [] + 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. + continue + newmodes.append(mpair) + + if origtype == str: + # If the original query is a string, send it back as a string. + return joinModes(newmodes) + else: + return set(newmodes) def isInternalClient(irc, numeric): """