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:
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)
|
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()
|
||||||
|
@ -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).
|
||||||
|
|
||||||
|
@ -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])
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user