3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-30 14:49:28 +01:00

core: move reverseModes, joinModes into Irc

This commit is contained in:
James Lu 2016-04-30 16:33:46 -07:00
parent 05752d9f60
commit 064cb9b6aa
10 changed files with 146 additions and 144 deletions

View File

@ -653,6 +653,134 @@ class Irc():
else: else:
self.channels[target].modes = modelist 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 ### State checking functions
def nickToUid(self, nick): def nickToUid(self, nick):
"""Looks up the UID of a user with the given nick, if one is present.""" """Looks up the UID of a user with the given nick, if one is present."""

View File

@ -121,7 +121,7 @@ def handle_whois(irc, source, command, args):
# Only show this to opers! # Only show this to opers!
if sourceisOper: if sourceisOper:
f(server, 378, source, "%s :is connecting from %s@%s %s" % (nick, user.ident, user.realhost, user.ip)) 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 # 301: used to show away information if present
away_text = user.away away_text = user.away

View File

@ -101,7 +101,7 @@ def showuser(irc, source, args):
if verbose: # Oper only data: user modes, channels on, account info, etc. 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' % \ f('\x02Protocol UID\x02: %s; \x02Real host\x02: %s; \x02IP\x02: %s' % \
(u, userobj.realhost, userobj.ip)) (u, userobj.realhost, userobj.ip))
channels = sorted(userobj.channels) channels = sorted(userobj.channels)
@ -141,7 +141,7 @@ def showchan(irc, source, args):
f('\x02Channel topic\x02: %s' % c.topic) f('\x02Channel topic\x02: %s' % c.topic)
f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts)) f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts))
# Show only modes that aren't list-style modes. # 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) f('\x02Channel modes\x02: %s' % modes)
if verbose: if verbose:
nicklist = [] nicklist = []

View File

@ -1022,7 +1022,7 @@ def handle_mode(irc, numeric, command, args):
if checkClaim(irc, target, numeric, chanobj=oldchan): if checkClaim(irc, target, numeric, chanobj=oldchan):
relayModes(irc, remoteirc, numeric, target, modes) relayModes(irc, remoteirc, numeric, target, modes)
else: # Mode change blocked by CLAIM. 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).', log.debug('(%s) relay.handle_mode: Reversing mode changes of %r with %r (CLAIM).',
irc.name, modes, reversed_modes) irc.name, modes, reversed_modes)
irc.proto.mode(irc.pseudoclient.uid, target, reversed_modes) irc.proto.mode(irc.pseudoclient.uid, target, reversed_modes)

View File

@ -108,7 +108,7 @@ class HybridProtocol(TS6Protocol):
ts = ts or int(time.time()) ts = ts or int(time.time())
realname = realname or self.irc.botdata['realname'] realname = realname or self.irc.botdata['realname']
realhost = realhost or host 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, u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable) realhost=realhost, ip=ip, manipulatable=manipulatable)
self.irc.applyModes(uid, modes) self.irc.applyModes(uid, modes)

View File

@ -55,7 +55,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
ts = ts or int(time.time()) ts = ts or int(time.time())
realname = realname or self.irc.botdata['realname'] realname = realname or self.irc.botdata['realname']
realhost = realhost or host 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, u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype) 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']] 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( self._send(server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
ts=self.irc.channels[channel].ts, uid=client, channel=channel, 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.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel) self.irc.users[client].channels.add(channel)
@ -136,7 +136,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
namelist = ' '.join(namelist) namelist = ' '.join(namelist)
self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format( self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel, ts=ts, users=namelist, channel=channel,
modes=utils.joinModes(modes))) modes=self.irc.joinModes(modes)))
self.irc.channels[channel].users.update(uids) self.irc.channels[channel].users.update(uids)
def _operUp(self, target, opertype=None): def _operUp(self, target, opertype=None):
@ -178,7 +178,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
# Servers need a special command to set umode +o on people. # Servers need a special command to set umode +o on people.
self._operUp(target) self._operUp(target)
self.irc.applyModes(target, modes) self.irc.applyModes(target, modes)
joinedmodes = utils.joinModes(modes) joinedmodes = self.irc.joinModes(modes)
if utils.isChannel(target): if utils.isChannel(target):
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes)) self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))

View File

@ -262,7 +262,7 @@ class P10Protocol(Protocol):
ts = ts or int(time.time()) ts = ts or int(time.time())
realname = realname or self.irc.botdata['realname'] realname = realname or self.irc.botdata['realname']
realhost = realhost or host realhost = realhost or host
raw_modes = utils.joinModes(modes) raw_modes = self.irc.joinModes(modes)
# Initialize an IrcUser instance # Initialize an IrcUser instance
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, 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 send_ts = False
while modes[:12]: 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:] modes = modes[12:]
self._send(numeric, 'M %s %s%s' % (target, joinedmodes, ' %s' % ts if send_ts else '')) 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. if modes: # Only send modes if there are any.
self._send(server, "B {channel} {ts} {modes} :{users}".format( self._send(server, "B {channel} {ts} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel, ts=ts, users=namelist, channel=channel,
modes=utils.joinModes(modes))) modes=self.irc.joinModes(modes)))
else: else:
self._send(server, "B {channel} {ts} :{users}".format( self._send(server, "B {channel} {ts} :{users}".format(
ts=ts, users=namelist, channel=channel)) ts=ts, users=namelist, channel=channel))

View File

@ -50,7 +50,7 @@ class TS6Protocol(TS6BaseProtocol):
ts = ts or int(time.time()) ts = ts or int(time.time())
realname = realname or self.irc.botdata['realname'] realname = realname or self.irc.botdata['realname']
realhost = realhost or host 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, u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype) realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
@ -135,7 +135,7 @@ class TS6Protocol(TS6BaseProtocol):
namelist = ' '.join(namelist) namelist = ' '.join(namelist)
self._send(server, "SJOIN {ts} {channel} {modes} :{users}".format( self._send(server, "SJOIN {ts} {channel} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel, ts=ts, users=namelist, channel=channel,
modes=utils.joinModes(modes))) modes=self.irc.joinModes(modes)))
self.irc.channels[channel].users.update(uids) self.irc.channels[channel].users.update(uids)
if ts <= orig_ts: if ts <= orig_ts:
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs. # 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]: while modes[:9]:
# Seriously, though. If you send more than 10 mode parameters in # Seriously, though. If you send more than 10 mode parameters in
# a line, charybdis will silently REJECT the entire command! # 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:] modes = modes[9:]
self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes)) self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes))
else: else:
joinedmodes = utils.joinModes(modes) joinedmodes = self.irc.joinModes(modes)
self._send(numeric, 'MODE %s %s' % (target, joinedmodes)) self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
def kill(self, numeric, target, reason): def kill(self, numeric, target, reason):

View File

@ -80,7 +80,7 @@ class UnrealProtocol(TS6BaseProtocol):
ts = ts or int(time.time()) ts = ts or int(time.time())
realname = realname or self.irc.botdata['realname'] realname = realname or self.irc.botdata['realname']
realhost = realhost or host 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, u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype) realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
self.irc.applyModes(uid, modes) self.irc.applyModes(uid, modes)
@ -216,7 +216,7 @@ class UnrealProtocol(TS6BaseProtocol):
raise LookupError('No such PyLink client/server exists.') raise LookupError('No such PyLink client/server exists.')
self.irc.applyModes(target, modes) self.irc.applyModes(target, modes)
joinedmodes = utils.joinModes(modes) joinedmodes = self.irc.joinModes(modes)
if utils.isChannel(target): if utils.isChannel(target):
# The MODE command is used for channel mode changes only # The MODE command is used for channel mode changes only
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts

126
utils.py
View File

@ -132,132 +132,6 @@ def applyModes(irc, target, changedmodes):
log.warning("(%s) utils.applyModes is deprecated. Use irc.applyModes() instead!", irc.name) log.warning("(%s) utils.applyModes is deprecated. Use irc.applyModes() instead!", irc.name)
return irc.applyModes(target, changedmodes) 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): def isOper(irc, uid, allowAuthed=True, allowOper=True):
""" """
Returns whether the given user has operator status on PyLink. This can be achieved Returns whether the given user has operator status on PyLink. This can be achieved