From b22f6747850a5f57f08f12e8fffd1c58a0eca888 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 5 Jul 2015 12:48:39 -0700 Subject: [PATCH] Support prefix modes (+qaohv); refactor applyModes to apply in place; add removeuser() to IrcChannel Closes #16. --- classes.py | 15 ++++++++++----- protocols/inspircd.py | 39 +++++++++++---------------------------- utils.py | 38 +++++++++++++++++++++++++++++++------- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/classes.py b/classes.py index 59b1727..74bf2af 100644 --- a/classes.py +++ b/classes.py @@ -1,3 +1,5 @@ +from collections import defaultdict + class IrcUser(): def __init__(self, nick, ts, uid, ident='null', host='null', realname='PyLink dummy client', realhost='null', @@ -37,13 +39,16 @@ class IrcChannel(): def __init__(self): self.users = set() self.modes = set() - ''' - self.ops = [] - self.halfops = [] - self.voices = [] - ''' + self.prefixmodes = {'ops': set(), 'halfops': set(), 'voices': set(), + 'owners': set(), 'admins': set()} + def __repr__(self): return repr(self.__dict__) + def removeuser(self, target): + for s in self.prefixmodes.values(): + s.discard(target) + self.users.discard(target) + class ProtocolError(Exception): pass diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 07fcdac..83fcbc6 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -73,11 +73,8 @@ def removeClient(irc, numeric): Removes a client from our internal databases, regardless of whether it's one of our pseudoclients or not.""" - for k, v in copy(irc.channels).items(): - irc.channels[k].users.discard(numeric) - if not irc.channels[k].users: - # Clear empty channels - del irc.channels[k] + for v in irc.channels.values(): + v.removeuser(source) sid = numeric[:3] print('Removing client %s from irc.users' % numeric) del irc.users[numeric] @@ -189,9 +186,7 @@ def handle_kick(irc, source, command, args): def handle_part(irc, source, command, args): channel = args[0].lower() # We should only get PART commands for channels that exist, right?? - irc.channels[channel].users.remove(source) - if not irc.channels[channel].users: - del irc.channels[channel] + irc.channels[channel].removeuser(source) try: reason = args[1] except IndexError: @@ -209,24 +204,12 @@ def handle_fjoin(irc, servernumeric, command, args): userlist = args[-1].split() ts = args[1] modestring = args[2:-1] or args[2] - irc.channels[channel].modes = utils.applyModes(irc.channels[channel].modes, utils.parseModes(irc, modestring)) + utils.applyModes(irc, channel, utils.parseModes(irc, channel, modestring)) namelist = [] for user in userlist: modeprefix, user = user.split(',', 1) namelist.append(user) - ''' - for mode in modeprefix: - # Note that a user can have more than one mode prefix (e.g. they have both +o and +v), - # so they would be added to both lists. - - # left to right: m_ojoin, m_operprefix, owner (~/+q), admin (&/+a), and op (!/+o) - if mode in 'Yyqao': - irc.channels[channel].ops.append(user) - if mode == 'h': - irc.channels[channel].halfops.append(user) - if mode == 'v': - irc.channels[channel].voices.append(user) - ''' + utils.applyModes(irc, channel, [('+%s' % mode, user) for mode in modeprefix]) irc.channels[channel].users.add(user) return {'channel': channel, 'users': namelist} @@ -235,9 +218,9 @@ def handle_uid(irc, numeric, command, args): uid, ts, nick, realhost, host, ident, ip = args[0:7] realname = args[-1] irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip) - parsedmodes = utils.parseModes(irc, [args[8], args[9]], usermodes=True) + parsedmodes = utils.parseModes(irc, uid, [args[8], args[9]]) print('Applying modes %s for %s' % (parsedmodes, uid)) - irc.users[uid].modes = utils.applyModes(irc.users[uid].modes, parsedmodes) + utils.applyModes(irc, uid, parsedmodes) irc.servers[numeric].users.append(uid) return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} @@ -284,8 +267,8 @@ def handle_fmode(irc, numeric, command, args): # <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD channel = args[0].lower() modes = args[2:] - changedmodes = utils.parseModes(irc, modes) - irc.channels[channel].modes = utils.applyModes(irc.channels[channel].modes, changedmodes) + changedmodes = utils.parseModes(irc, channel, modes) + utils.applyModes(irc, channel, changedmodes) return {'target': channel, 'modes': changedmodes} def handle_mode(irc, numeric, command, args): @@ -294,8 +277,8 @@ def handle_mode(irc, numeric, command, args): # <- :70MAAAAAA MODE 70MAAAAAA -i+xc target = args[0] modestrings = args[1:] - changedmodes = utils.parseModes(irc, modestrings, usermodes=True) - irc.users[numeric].modes = utils.applyModes(irc.users[numeric].modes, changedmodes) + changedmodes = utils.parseModes(irc, numeric, modestrings) + utils.applyModes(irc, numeric, changedmodes) return {'target': target, 'modes': changedmodes} def handle_squit(irc, numeric, command, args): diff --git a/utils.py b/utils.py index 8703f35..24dc332 100644 --- a/utils.py +++ b/utils.py @@ -83,7 +83,7 @@ def isServerName(s): return _isASCII(s) and '.' in s and not s.startswith('.') \ and not s.endswith('.') -def parseModes(irc, args, usermodes=False): +def parseModes(irc, target, args): """Parses a mode string into a list of (mode, argument) tuples. ['+mitl-o', '3', 'person'] => [('+m', None), ('+i', None), ('+t', None), ('+l', '3'), ('-o', 'person')] """ @@ -92,7 +92,7 @@ def parseModes(irc, args, usermodes=False): # 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. - print(args) + usermodes = not isChannel(target) modestring = args[0] if not modestring: return ValueError('No modes supplied in parseModes query: %r' % modes) @@ -117,8 +117,7 @@ def parseModes(irc, args, usermodes=False): elif mode in irc.prefixmodes and not usermodes: # We're setting a prefix mode on someone (e.g. +o user1) print('%s: prefixmode.' % mode) - # TODO: handle this properly (issue #16). - continue + arg = args.pop(0) elif prefix == '+' and mode in supported_modes['*C']: # Only has parameter when setting. print('%s: Only has parameter when setting.' % mode) @@ -126,11 +125,37 @@ def parseModes(irc, args, usermodes=False): res.append((prefix + mode, arg)) return res -def applyModes(modelist, changedmodes): - modelist = modelist.copy() +def applyModes(irc, target, changedmodes): + usermodes = not isChannel(target) + print('usermodes? %s' % usermodes) + if usermodes: + modelist = irc.users[target].modes + else: + modelist = irc.channels[target].modes print('Initial modelist: %s' % modelist) print('Changedmodes: %r' % changedmodes) for mode in changedmodes: + if not usermodes: + pmode = '' + for m in ('owner', 'admin', 'op', 'halfop', 'voice'): + if m in irc.cmodes and mode[0][1] == irc.cmodes[m]: + pmode = m+'s' + print('pmode? %s' % pmode) + if pmode: + print('pmode == True') + print(mode) + print(irc.channels[target].prefixmodes) + pmodelist = irc.channels[target].prefixmodes[pmode] + print(pmodelist) + print('Initial pmodelist: %s' % pmodelist) + if mode[0][0] == '+': + pmodelist.add(mode[1]) + print('+') + else: + pmodelist.discard(mode[1]) + print('-') + print('Final pmodelist: %s' % pmodelist) + continue if mode[0][0] == '+': # We're adding a mode modelist.add(mode) @@ -141,7 +166,6 @@ def applyModes(modelist, changedmodes): modelist.discard(mode) print('Removing mode %r' % str(mode)) print('Final modelist: %s' % modelist) - return modelist def joinModes(modes): modelist = ''