3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-24 03:29:28 +01:00

core/protocols: add modes option in sjoin(), TS6 BMASK, and mode TS rules in updateTS()

Closes #249.
Closes #250.
This commit is contained in:
James Lu 2016-06-22 21:34:16 -07:00
parent 6fc5fa3130
commit 6b8e80cd5b
5 changed files with 106 additions and 51 deletions

View File

@ -1067,24 +1067,37 @@ class Protocol():
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid) log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
self.irc.servers[sid].users.discard(numeric) self.irc.servers[sid].users.discard(numeric)
def updateTS(self, channel, their_ts): def updateTS(self, channel, their_ts, modes=[]):
""" """
Compares the current TS of the channel given with the new TS, resetting Merges modes of a channel given the remote TS and a list of modes.
all modes we have if the one given is older.
This returns True when our modes apply (our TS <= theirs)
""" """
our_ts = self.irc.channels[channel].ts our_ts = self.irc.channels[channel].ts
if their_ts < our_ts: if their_ts < our_ts:
# Channel timestamp was reset on burst # Their TS is older than ours. Clear all modes.
log.debug('(%s) Setting channel TS of %s to %s from %s', log.debug('(%s) Setting channel TS of %s to %s from %s',
self.irc.name, channel, their_ts, our_ts) self.irc.name, channel, their_ts, our_ts)
self.irc.channels[channel].ts = their_ts self.irc.channels[channel].ts = their_ts
# When TS is reset, clear all modes we currently have
self.irc.channels[channel].modes.clear() self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values(): for p in self.irc.channels[channel].prefixmodes.values():
p.clear() p.clear()
return False
elif their_ts == our_ts:
# Their TS is equal to ours. Merge modes.
self.irc.applyModes(channel, modes)
return True
elif their_ts > our_ts:
# Their TS is younger than ours. Replace their modes with ours.
self.irc.channels[channel].modes.clear()
self.irc.applyModes(channel, modes)
return True
def _getSid(self, sname): def _getSid(self, sname):
"""Returns the SID of a server with the given name, if present.""" """Returns the SID of a server with the given name, if present."""
name = sname.lower() name = sname.lower()

View File

@ -85,7 +85,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
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)
def sjoin(self, server, channel, users, ts=None): def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel. """Sends an SJOIN for a group of users to a channel.
The sender should always be a Server ID (SID). TS is optional, and defaults The sender should always be a Server ID (SID). TS is optional, and defaults
@ -100,20 +100,27 @@ class InspIRCdProtocol(TS6BaseProtocol):
server = server or self.irc.sid server = server or self.irc.sid
assert users, "sjoin: No users sent?" assert users, "sjoin: No users sent?"
log.debug('(%s) sjoin: got %r for users', self.irc.name, users) log.debug('(%s) sjoin: got %r for users', self.irc.name, users)
if not server: if not server:
raise LookupError('No such PyLink client exists.') raise LookupError('No such PyLink client exists.')
orig_ts = self.irc.channels[channel].ts
ts = ts or orig_ts
self.updateTS(channel, ts)
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, self.irc.name, ts,
time.strftime("%c", time.localtime(ts)))
# Strip out list-modes, they shouldn't ever be sent in FJOIN (protocol rules). # Strip out list-modes, they shouldn't ever be sent in FJOIN (protocol rules).
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']] modes = modes or self.irc.channels[channel].modes
banmodes = []
regularmodes = []
for mode in modes:
modechar = mode[0][-1]
# Don't reset bans that have already been set
if modechar in self.irc.cmodes['*A'] and (modechar, mode[1]) not in self.irc.channels[channel].modes:
banmodes.append(mode)
else:
regularmodes.append(mode)
uids = [] uids = []
changedmodes = [] changedmodes = set(modes)
namelist = [] namelist = []
# We take <users> as a list of (prefixmodes, uid) pairs. # We take <users> as a list of (prefixmodes, uid) pairs.
for userpair in users: for userpair in users:
assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair
@ -121,20 +128,28 @@ class InspIRCdProtocol(TS6BaseProtocol):
namelist.append(','.join(userpair)) namelist.append(','.join(userpair))
uids.append(user) uids.append(user)
for m in prefixes: for m in prefixes:
changedmodes.append(('+%s' % m, user)) changedmodes.add(('+%s' % m, user))
try: try:
self.irc.users[user].channels.add(channel) self.irc.users[user].channels.add(channel)
except KeyError: # Not initialized yet? except KeyError: # Not initialized yet?
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user) log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
if ts <= orig_ts:
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
self.irc.applyModes(channel, changedmodes)
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=self.irc.joinModes(modes))) modes=self.irc.joinModes(modes)))
self.irc.channels[channel].users.update(uids) self.irc.channels[channel].users.update(uids)
if banmodes:
# Burst ban modes if there are any.
# <- :1ML FMODE #test 1461201525 +bb *!*@bad.user *!*@rly.bad.user
self._send(server, "FMODE {channel} {ts} {modes} ".format(
ts=ts, channel=channel, modes=self.irc.joinModes(banmodes)))
orig_ts = self.irc.channels[channel].ts
ts = ts or orig_ts
self.updateTS(channel, ts, changedmodes)
def _operUp(self, target, opertype=None): def _operUp(self, target, opertype=None):
"""Opers a client up (internal function specific to InspIRCd). """Opers a client up (internal function specific to InspIRCd).

View File

@ -450,7 +450,7 @@ class P10Protocol(Protocol):
else: else:
raise LookupError("No such PyLink client exists.") raise LookupError("No such PyLink client exists.")
def sjoin(self, server, channel, users, ts=None): def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel. """Sends an SJOIN for a group of users to a channel.
The sender should always be a Server ID (SID). TS is optional, and defaults The sender should always be a Server ID (SID). TS is optional, and defaults
@ -470,14 +470,11 @@ class P10Protocol(Protocol):
if not server: if not server:
raise LookupError('No such PyLink client exists.') raise LookupError('No such PyLink client exists.')
orig_ts = self.irc.channels[channel].ts
ts = ts or orig_ts
self.updateTS(channel, ts)
# Only send non-list modes in BURST. TODO: burst bans and banexempts too # Only send non-list modes in BURST. TODO: burst bans and banexempts too
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']] modes = modes or self.irc.channels[channel].modes
modes = [m for m in modes if m[0] not in self.irc.cmodes['*A']]
changedmodes = [] changedmodes = modes
changedusers = [] changedusers = []
namelist = [] namelist = []
@ -532,9 +529,9 @@ class P10Protocol(Protocol):
self.irc.channels[channel].users.update(changedusers) self.irc.channels[channel].users.update(changedusers)
if ts <= orig_ts: orig_ts = self.irc.channels[channel].ts
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs. ts = ts or orig_ts
self.irc.applyModes(channel, changedmodes) self.updateTS(channel, ts, changedmodes)
def spawnServer(self, name, sid=None, uplink=None, desc=None, endburst_delay=0): def spawnServer(self, name, sid=None, uplink=None, desc=None, endburst_delay=0):
""" """
@ -1032,9 +1029,11 @@ class P10Protocol(Protocol):
oldchans = self.irc.users[numeric].channels.copy() oldchans = self.irc.users[numeric].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r', log.debug('(%s) Got /join 0 from %r, channel list is %r',
self.irc.name, numeric, oldchans) self.irc.name, numeric, oldchans)
for channel in oldchans: for channel in oldchans:
self.irc.channels[channel].users.discard(source) self.irc.channels[channel].users.discard(source)
self.irc.users[source].channels.discard(channel) self.irc.users[source].channels.discard(channel)
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'} return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
else: else:
channel = self.irc.toLower(args[0]) channel = self.irc.toLower(args[0])

View File

@ -73,7 +73,7 @@ class TS6Protocol(TS6BaseProtocol):
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)
def sjoin(self, server, channel, users, ts=None): def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel. """Sends an SJOIN for a group of users to a channel.
The sender should always be a Server ID (SID). TS is optional, and defaults The sender should always be a Server ID (SID). TS is optional, and defaults
@ -100,14 +100,23 @@ class TS6Protocol(TS6BaseProtocol):
if not server: if not server:
raise LookupError('No such PyLink client exists.') raise LookupError('No such PyLink client exists.')
orig_ts = self.irc.channels[channel].ts modes = set(modes or self.irc.channels[channel].modes)
ts = ts or orig_ts
self.updateTS(channel, ts)
log.debug("(%s) sending SJOIN to %s with ts %s (that's %r)", self.irc.name, channel, ts, # Get all the ban modes in a separate list. These are bursted using a separate BMASK
time.strftime("%c", time.localtime(ts))) # command.
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']] banmodes = {k: set() for k in self.irc.cmodes['*A']}
changedmodes = [] regularmodes = []
log.debug('(%s) Unfiltered SJOIN modes: %s', self.irc.name, modes)
for mode in modes:
modechar = mode[0][-1]
if modechar in self.irc.cmodes['*A']:
# Mode character is one of 'beIq'
banmodes[modechar].add(mode[1])
else:
regularmodes.append(mode)
log.debug('(%s) Filtered SJOIN modes to be regular modes: %s, banmodes: %s', self.irc.name, regularmodes, banmodes)
changedmodes = modes
while users[:10]: while users[:10]:
uids = [] uids = []
namelist = [] namelist = []
@ -120,7 +129,7 @@ class TS6Protocol(TS6BaseProtocol):
pr = self.irc.prefixmodes.get(prefix) pr = self.irc.prefixmodes.get(prefix)
if pr: if pr:
prefixchars += pr prefixchars += pr
changedmodes.append(('+%s' % prefix, user)) changedmodes.add(('+%s' % prefix, user))
namelist.append(prefixchars+user) namelist.append(prefixchars+user)
uids.append(user) uids.append(user)
try: try:
@ -131,11 +140,20 @@ 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=self.irc.joinModes(modes))) modes=self.irc.joinModes(regularmodes)))
self.irc.channels[channel].users.update(uids) 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. # Now, burst bans.
self.irc.applyModes(channel, changedmodes) # <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
for bmode, bans in banmodes.items():
if bans:
log.debug('(%s) sjoin: bursting mode %s with bans %s', self.irc.name, bmode, bans)
self._send(server, "BMASK {ts} {channel} {bmode} :{bans}".format(ts=ts,
channel=channel, bmode=bmode, bans=' '.join(bans)))
orig_ts = self.irc.channels[channel].ts
ts = ts or orig_ts
self.updateTS(channel, ts, changedmodes)
def mode(self, numeric, target, modes, ts=None): def mode(self, numeric, target, modes, ts=None):
"""Sends mode changes from a PyLink client/server.""" """Sends mode changes from a PyLink client/server."""

View File

@ -123,7 +123,7 @@ class UnrealProtocol(TS6BaseProtocol):
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)
def sjoin(self, server, channel, users, ts=None): def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel. """Sends an SJOIN for a group of users to a channel.
The sender should always be a server (SID). TS is optional, and defaults The sender should always be a server (SID). TS is optional, and defaults
@ -147,36 +147,46 @@ class UnrealProtocol(TS6BaseProtocol):
if not server: if not server:
raise LookupError('No such PyLink server exists.') raise LookupError('No such PyLink server exists.')
orig_ts = self.irc.channels[channel].ts changedmodes = set(modes or self.irc.channels[channel].modes)
ts = ts or orig_ts
self.updateTS(channel, ts)
changedmodes = []
uids = [] uids = []
namelist = [] namelist = []
for userpair in users: for userpair in users:
assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair
prefixes, user = userpair prefixes, user = userpair
# Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~, # Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~,
# and +a is ~ instead of &. # and +a is ~ instead of &.
# &, ", and ' are used for bursting bans. # &, ", and ' are used for bursting bans.
sjoin_prefixes = {'q': '*', 'a': '~', 'o': '@', 'h': '%', 'v': '+'} sjoin_prefixes = {'q': '*', 'a': '~', 'o': '@', 'h': '%', 'v': '+'}
prefixchars = ''.join([sjoin_prefixes.get(prefix, '') for prefix in prefixes]) prefixchars = ''.join([sjoin_prefixes.get(prefix, '') for prefix in prefixes])
if prefixchars: if prefixchars:
changedmodes + [('+%s' % prefix, user) for prefix in prefixes] changedmodes |= {('+%s' % prefix, user) for prefix in prefixes}
namelist.append(prefixchars+user) namelist.append(prefixchars+user)
uids.append(user) uids.append(user)
try: try:
self.irc.users[user].channels.add(channel) self.irc.users[user].channels.add(channel)
except KeyError: # Not initialized yet? except KeyError: # Not initialized yet?
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user) log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
namelist = ' '.join(namelist) namelist = ' '.join(namelist)
self._send(server, "SJOIN {ts} {channel} :{users}".format( self._send(server, "SJOIN {ts} {channel} :{users}".format(
ts=ts, users=namelist, channel=channel)) ts=ts, users=namelist, channel=channel))
# Burst modes separately. No really, this is what I see UnrealIRCd do! It sends
# JOINs on burst and then MODE!
if modes:
self.mode(server, channel, modes, ts=ts)
self.irc.channels[channel].users.update(uids) 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. orig_ts = self.irc.channels[channel].ts
self.irc.applyModes(channel, changedmodes) ts = ts or orig_ts
self.updateTS(channel, ts, changedmodes)
def ping(self, source=None, target=None): def ping(self, source=None, target=None):
"""Sends a PING to a target server. Periodic PINGs are sent to our uplink """Sends a PING to a target server. Periodic PINGs are sent to our uplink