mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-27 13:09:23 +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:
parent
6fc5fa3130
commit
6b8e80cd5b
23
classes.py
23
classes.py
@ -1067,24 +1067,37 @@ class Protocol():
|
||||
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
|
||||
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
|
||||
all modes we have if the one given is older.
|
||||
Merges modes of a channel given the remote TS and a list of modes.
|
||||
|
||||
This returns True when our modes apply (our TS <= theirs)
|
||||
"""
|
||||
|
||||
our_ts = self.irc.channels[channel].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',
|
||||
self.irc.name, channel, their_ts, our_ts)
|
||||
self.irc.channels[channel].ts = their_ts
|
||||
# When TS is reset, clear all modes we currently have
|
||||
|
||||
self.irc.channels[channel].modes.clear()
|
||||
for p in self.irc.channels[channel].prefixmodes.values():
|
||||
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):
|
||||
"""Returns the SID of a server with the given name, if present."""
|
||||
name = sname.lower()
|
||||
|
@ -85,7 +85,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
self.irc.channels[channel].users.add(client)
|
||||
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.
|
||||
|
||||
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
|
||||
assert users, "sjoin: No users sent?"
|
||||
log.debug('(%s) sjoin: got %r for users', self.irc.name, users)
|
||||
|
||||
if not server:
|
||||
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).
|
||||
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 = []
|
||||
changedmodes = []
|
||||
changedmodes = set(modes)
|
||||
namelist = []
|
||||
|
||||
# We take <users> as a list of (prefixmodes, uid) pairs.
|
||||
for userpair in users:
|
||||
assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair
|
||||
@ -121,20 +128,28 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
||||
namelist.append(','.join(userpair))
|
||||
uids.append(user)
|
||||
for m in prefixes:
|
||||
changedmodes.append(('+%s' % m, user))
|
||||
changedmodes.add(('+%s' % m, user))
|
||||
try:
|
||||
self.irc.users[user].channels.add(channel)
|
||||
except KeyError: # Not initialized yet?
|
||||
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)
|
||||
self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
||||
ts=ts, users=namelist, channel=channel,
|
||||
modes=self.irc.joinModes(modes)))
|
||||
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):
|
||||
"""Opers a client up (internal function specific to InspIRCd).
|
||||
|
||||
|
@ -450,7 +450,7 @@ class P10Protocol(Protocol):
|
||||
else:
|
||||
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.
|
||||
|
||||
The sender should always be a Server ID (SID). TS is optional, and defaults
|
||||
@ -470,14 +470,11 @@ class P10Protocol(Protocol):
|
||||
if not server:
|
||||
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
|
||||
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 = []
|
||||
namelist = []
|
||||
|
||||
@ -532,9 +529,9 @@ class P10Protocol(Protocol):
|
||||
|
||||
self.irc.channels[channel].users.update(changedusers)
|
||||
|
||||
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)
|
||||
orig_ts = self.irc.channels[channel].ts
|
||||
ts = ts or orig_ts
|
||||
self.updateTS(channel, ts, changedmodes)
|
||||
|
||||
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()
|
||||
log.debug('(%s) Got /join 0 from %r, channel list is %r',
|
||||
self.irc.name, numeric, oldchans)
|
||||
|
||||
for channel in oldchans:
|
||||
self.irc.channels[channel].users.discard(source)
|
||||
self.irc.users[source].channels.discard(channel)
|
||||
|
||||
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
|
||||
else:
|
||||
channel = self.irc.toLower(args[0])
|
||||
|
@ -73,7 +73,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
self.irc.channels[channel].users.add(client)
|
||||
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.
|
||||
|
||||
The sender should always be a Server ID (SID). TS is optional, and defaults
|
||||
@ -100,14 +100,23 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
if not server:
|
||||
raise LookupError('No such PyLink client exists.')
|
||||
|
||||
orig_ts = self.irc.channels[channel].ts
|
||||
ts = ts or orig_ts
|
||||
self.updateTS(channel, ts)
|
||||
modes = set(modes or self.irc.channels[channel].modes)
|
||||
|
||||
log.debug("(%s) sending SJOIN to %s with ts %s (that's %r)", self.irc.name, channel, ts,
|
||||
time.strftime("%c", time.localtime(ts)))
|
||||
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
|
||||
changedmodes = []
|
||||
# Get all the ban modes in a separate list. These are bursted using a separate BMASK
|
||||
# command.
|
||||
banmodes = {k: set() for k in self.irc.cmodes['*A']}
|
||||
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]:
|
||||
uids = []
|
||||
namelist = []
|
||||
@ -120,7 +129,7 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
pr = self.irc.prefixmodes.get(prefix)
|
||||
if pr:
|
||||
prefixchars += pr
|
||||
changedmodes.append(('+%s' % prefix, user))
|
||||
changedmodes.add(('+%s' % prefix, user))
|
||||
namelist.append(prefixchars+user)
|
||||
uids.append(user)
|
||||
try:
|
||||
@ -131,11 +140,20 @@ class TS6Protocol(TS6BaseProtocol):
|
||||
namelist = ' '.join(namelist)
|
||||
self._send(server, "SJOIN {ts} {channel} {modes} :{users}".format(
|
||||
ts=ts, users=namelist, channel=channel,
|
||||
modes=self.irc.joinModes(modes)))
|
||||
modes=self.irc.joinModes(regularmodes)))
|
||||
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.
|
||||
self.irc.applyModes(channel, changedmodes)
|
||||
|
||||
# Now, burst bans.
|
||||
# <- :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):
|
||||
"""Sends mode changes from a PyLink client/server."""
|
||||
|
@ -123,7 +123,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
self.irc.channels[channel].users.add(client)
|
||||
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.
|
||||
|
||||
The sender should always be a server (SID). TS is optional, and defaults
|
||||
@ -147,36 +147,46 @@ class UnrealProtocol(TS6BaseProtocol):
|
||||
if not server:
|
||||
raise LookupError('No such PyLink server exists.')
|
||||
|
||||
orig_ts = self.irc.channels[channel].ts
|
||||
ts = ts or orig_ts
|
||||
self.updateTS(channel, ts)
|
||||
|
||||
changedmodes = []
|
||||
changedmodes = set(modes or self.irc.channels[channel].modes)
|
||||
uids = []
|
||||
namelist = []
|
||||
|
||||
for userpair in users:
|
||||
assert len(userpair) == 2, "Incorrect format of userpair: %r" % userpair
|
||||
prefixes, user = userpair
|
||||
|
||||
# Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~,
|
||||
# and +a is ~ instead of &.
|
||||
# &, ", and ' are used for bursting bans.
|
||||
sjoin_prefixes = {'q': '*', 'a': '~', 'o': '@', 'h': '%', 'v': '+'}
|
||||
prefixchars = ''.join([sjoin_prefixes.get(prefix, '') for prefix in prefixes])
|
||||
|
||||
if prefixchars:
|
||||
changedmodes + [('+%s' % prefix, user) for prefix in prefixes]
|
||||
changedmodes |= {('+%s' % prefix, user) for prefix in prefixes}
|
||||
|
||||
namelist.append(prefixchars+user)
|
||||
uids.append(user)
|
||||
|
||||
try:
|
||||
self.irc.users[user].channels.add(channel)
|
||||
except KeyError: # Not initialized yet?
|
||||
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
|
||||
|
||||
namelist = ' '.join(namelist)
|
||||
|
||||
self._send(server, "SJOIN {ts} {channel} :{users}".format(
|
||||
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)
|
||||
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)
|
||||
|
||||
orig_ts = self.irc.channels[channel].ts
|
||||
ts = ts or orig_ts
|
||||
self.updateTS(channel, ts, changedmodes)
|
||||
|
||||
def ping(self, source=None, target=None):
|
||||
"""Sends a PING to a target server. Periodic PINGs are sent to our uplink
|
||||
|
Loading…
Reference in New Issue
Block a user