mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-25 04:02:45 +01:00
core: define two (joined) versions of the channels index
Closes #509. PyLinkNetworkCore.channels is split into the following: - irc._channels which implicitly creates channels on access (mostly used in protocol modules) - irc.channels which does not (recommended for use by plugins)
This commit is contained in:
parent
f34198647e
commit
80766e051e
29
classes.py
29
classes.py
@ -130,11 +130,16 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
|
|||||||
self.called_in = None
|
self.called_in = None
|
||||||
|
|
||||||
# Intialize the server, channel, and user indexes to be populated by
|
# Intialize the server, channel, and user indexes to be populated by
|
||||||
# our protocol module. For the server index, we can add ourselves right
|
# our protocol module.
|
||||||
# now.
|
|
||||||
self.servers = {}
|
self.servers = {}
|
||||||
self.users = {}
|
self.users = {}
|
||||||
self.channels = ChannelState(self)
|
|
||||||
|
# Two versions of the channels index exist in PyLink 2.0, and they are joined together
|
||||||
|
# - irc._channels which implicitly creates channels on access (mostly used
|
||||||
|
# in protocol modules)
|
||||||
|
# - irc.channels which does not (recommended for use by plugins)
|
||||||
|
self._channels = ChannelState(self)
|
||||||
|
self.channels = structures.IRCCaseInsensitiveDict(self, data=self._channels._data)
|
||||||
|
|
||||||
# This sets the list of supported channel and user modes: the default
|
# This sets the list of supported channel and user modes: the default
|
||||||
# RFC1459 modes are implied. Named modes are used here to make
|
# RFC1459 modes are implied. Named modes are used here to make
|
||||||
@ -531,7 +536,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
log.debug('(%s) Using self.cmodes for this query: %s', self.name, self.cmodes)
|
log.debug('(%s) Using self.cmodes for this query: %s', self.name, self.cmodes)
|
||||||
|
|
||||||
supported_modes = self.cmodes
|
supported_modes = self.cmodes
|
||||||
oldmodes = self.channels[target].modes
|
oldmodes = self._channels[target].modes
|
||||||
res = []
|
res = []
|
||||||
for mode in modestring:
|
for mode in modestring:
|
||||||
if mode in '+-':
|
if mode in '+-':
|
||||||
@ -601,7 +606,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
old_modelist = self.users[target].modes
|
old_modelist = self.users[target].modes
|
||||||
supported_modes = self.umodes
|
supported_modes = self.umodes
|
||||||
else:
|
else:
|
||||||
old_modelist = self.channels[target].modes
|
old_modelist = self._channels[target].modes
|
||||||
supported_modes = self.cmodes
|
supported_modes = self.cmodes
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.warning('(%s) Possible desync? Mode target %s is unknown.', self.name, target)
|
log.warning('(%s) Possible desync? Mode target %s is unknown.', self.name, target)
|
||||||
@ -621,7 +626,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
# if the IRCd supports this mode and it is the one being set, add/remove
|
# if the IRCd supports this mode and it is the one being set, add/remove
|
||||||
# the person from the corresponding prefix mode list (e.g. c.prefixmodes['op']
|
# the person from the corresponding prefix mode list (e.g. c.prefixmodes['op']
|
||||||
# for ops).
|
# for ops).
|
||||||
for pmode, pmodelist in self.channels[target].prefixmodes.items():
|
for pmode, pmodelist in self._channels[target].prefixmodes.items():
|
||||||
if pmode in self.cmodes and real_mode[0] == self.cmodes[pmode]:
|
if pmode in self.cmodes and real_mode[0] == self.cmodes[pmode]:
|
||||||
log.debug('(%s) Initial prefixmodes list: %s', self.name, pmodelist)
|
log.debug('(%s) Initial prefixmodes list: %s', self.name, pmodelist)
|
||||||
if mode[0][0] == '+':
|
if mode[0][0] == '+':
|
||||||
@ -668,7 +673,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
if usermodes:
|
if usermodes:
|
||||||
self.users[target].modes = modelist
|
self.users[target].modes = modelist
|
||||||
else:
|
else:
|
||||||
self.channels[target].modes = modelist
|
self._channels[target].modes = modelist
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.warning("(%s) Invalid MODE target %s (usermodes=%s)", self.name, target, usermodes)
|
log.warning("(%s) Invalid MODE target %s (usermodes=%s)", self.name, target, usermodes)
|
||||||
|
|
||||||
@ -706,7 +711,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
modes = self.parse_modes(target, modes.split(" "))
|
modes = self.parse_modes(target, modes.split(" "))
|
||||||
# Get the current mode list first.
|
# Get the current mode list first.
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
c = oldobj or self.channels[target]
|
c = oldobj or self._channels[target]
|
||||||
oldmodes = c.modes.copy()
|
oldmodes = c.modes.copy()
|
||||||
possible_modes = self.cmodes.copy()
|
possible_modes = self.cmodes.copy()
|
||||||
# For channels, this also includes the list of prefix modes.
|
# For channels, this also includes the list of prefix modes.
|
||||||
@ -1116,8 +1121,8 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
def _clear():
|
def _clear():
|
||||||
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.name,
|
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.name,
|
||||||
channel)
|
channel)
|
||||||
self.channels[channel].modes.clear()
|
self._channels[channel].modes.clear()
|
||||||
for p in self.channels[channel].prefixmodes.values():
|
for p in self._channels[channel].prefixmodes.values():
|
||||||
for user in p.copy():
|
for user in p.copy():
|
||||||
if not self.is_internal_client(user):
|
if not self.is_internal_client(user):
|
||||||
p.discard(user)
|
p.discard(user)
|
||||||
@ -1131,7 +1136,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
# Use a lock so only one thread can change a channel's TS at once: this prevents race
|
# Use a lock so only one thread can change a channel's TS at once: this prevents race
|
||||||
# conditions that would otherwise desync channel modes.
|
# conditions that would otherwise desync channel modes.
|
||||||
with self._ts_lock:
|
with self._ts_lock:
|
||||||
our_ts = self.channels[channel].ts
|
our_ts = self._channels[channel].ts
|
||||||
assert isinstance(our_ts, int), "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
|
assert isinstance(our_ts, int), "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
|
||||||
assert isinstance(their_ts, int), "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
|
assert isinstance(their_ts, int), "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
|
||||||
|
|
||||||
@ -1153,7 +1158,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
|
|||||||
else:
|
else:
|
||||||
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
|
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
|
||||||
self.name, channel, our_ts, their_ts)
|
self.name, channel, our_ts, their_ts)
|
||||||
self.channels[channel].ts = their_ts
|
self._channels[channel].ts = their_ts
|
||||||
|
|
||||||
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
|
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
if self.pseudoclient and client == self.pseudoclient.uid:
|
if self.pseudoclient and client == self.pseudoclient.uid:
|
||||||
self.send('JOIN %s' % channel)
|
self.send('JOIN %s' % channel)
|
||||||
else:
|
else:
|
||||||
self.channels[channel].users.add(client)
|
self._channels[channel].users.add(client)
|
||||||
self.users[client].channels.add(channel)
|
self.users[client].channels.add(channel)
|
||||||
|
|
||||||
log.debug('(%s) join: faking JOIN of client %s/%s to %s', self.name, client,
|
log.debug('(%s) join: faking JOIN of client %s/%s to %s', self.name, client,
|
||||||
@ -272,7 +272,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
|
|
||||||
def part(self, source, channel, reason=''):
|
def part(self, source, channel, reason=''):
|
||||||
"""STUB: Parts a user from a channel."""
|
"""STUB: Parts a user from a channel."""
|
||||||
self.channels[channel].remove_user(source)
|
self._channels[channel].remove_user(source)
|
||||||
self.users[source].channels.discard(channel)
|
self.users[source].channels.discard(channel)
|
||||||
|
|
||||||
# Only parts for the main PyLink client are actually forwarded. Others are ignored.
|
# Only parts for the main PyLink client are actually forwarded. Others are ignored.
|
||||||
@ -300,7 +300,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
# Otherwise, track the state for our virtual clients.
|
# Otherwise, track the state for our virtual clients.
|
||||||
self.users[user].channels.add(channel)
|
self.users[user].channels.add(channel)
|
||||||
|
|
||||||
self.channels[channel].users |= puids
|
self._channels[channel].users |= puids
|
||||||
nicks = {self.get_friendly_name(u) for u in puids}
|
nicks = {self.get_friendly_name(u) for u in puids}
|
||||||
self.call_hooks([server, 'CLIENTBOT_SJOIN', {'channel': channel, 'nicks': nicks}])
|
self.call_hooks([server, 'CLIENTBOT_SJOIN', {'channel': channel, 'nicks': nicks}])
|
||||||
|
|
||||||
@ -671,7 +671,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
|
|
||||||
# Queue these virtual users to be joined if they're not already in the channel,
|
# Queue these virtual users to be joined if they're not already in the channel,
|
||||||
# or we're waiting for a kick acknowledgment for them.
|
# or we're waiting for a kick acknowledgment for them.
|
||||||
if (idsource not in self.channels[channel].users) or (idsource in \
|
if (idsource not in self._channels[channel].users) or (idsource in \
|
||||||
self.kick_queue.get(channel, ([],))[0]):
|
self.kick_queue.get(channel, ([],))[0]):
|
||||||
names.add(idsource)
|
names.add(idsource)
|
||||||
self.users[idsource].channels.add(channel)
|
self.users[idsource].channels.add(channel)
|
||||||
@ -686,7 +686,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Statekeeping: make sure the channel's user list is updated!
|
# Statekeeping: make sure the channel's user list is updated!
|
||||||
self.channels[channel].users |= names
|
self._channels[channel].users |= names
|
||||||
self.apply_modes(channel, modes)
|
self.apply_modes(channel, modes)
|
||||||
|
|
||||||
log.debug('(%s) handle_353: adding users %s to %s', self.name, names, channel)
|
log.debug('(%s) handle_353: adding users %s to %s', self.name, names, channel)
|
||||||
@ -698,7 +698,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
fully_synced_names = [uid for uid in names if hasattr(self.users[uid], '_clientbot_identhost_received')]
|
fully_synced_names = [uid for uid in names if hasattr(self.users[uid], '_clientbot_identhost_received')]
|
||||||
if fully_synced_names:
|
if fully_synced_names:
|
||||||
log.debug('(%s) handle_353: sending pre-WHO JOIN hook for %s: %s', self.name, channel, fully_synced_names)
|
log.debug('(%s) handle_353: sending pre-WHO JOIN hook for %s: %s', self.name, channel, fully_synced_names)
|
||||||
return {'channel': channel, 'users': fully_synced_names, 'modes': self.channels[channel].modes,
|
return {'channel': channel, 'users': fully_synced_names, 'modes': self._channels[channel].modes,
|
||||||
'parse_as': "JOIN"}
|
'parse_as': "JOIN"}
|
||||||
|
|
||||||
def _check_puid_collision(self, nick):
|
def _check_puid_collision(self, nick):
|
||||||
@ -796,7 +796,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
self.who_received.clear()
|
self.who_received.clear()
|
||||||
|
|
||||||
channel = args[1]
|
channel = args[1]
|
||||||
c = self.channels[channel]
|
c = self._channels[channel]
|
||||||
|
|
||||||
modes = set(c.modes)
|
modes = set(c.modes)
|
||||||
for user in users.copy():
|
for user in users.copy():
|
||||||
@ -848,7 +848,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
# With extended-join:
|
# With extended-join:
|
||||||
# <- :GL|!~GL@127.0.0.1 JOIN #whatever accountname :realname
|
# <- :GL|!~GL@127.0.0.1 JOIN #whatever accountname :realname
|
||||||
channel = args[0]
|
channel = args[0]
|
||||||
self.channels[channel].users.add(source)
|
self._channels[channel].users.add(source)
|
||||||
self.users[source].channels.add(channel)
|
self.users[source].channels.add(channel)
|
||||||
|
|
||||||
if len(args) >= 3:
|
if len(args) >= 3:
|
||||||
@ -864,7 +864,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
self._send_who(channel)
|
self._send_who(channel)
|
||||||
else:
|
else:
|
||||||
self.call_hooks([source, 'CLIENTBOT_JOIN', {'channel': channel}])
|
self.call_hooks([source, 'CLIENTBOT_JOIN', {'channel': channel}])
|
||||||
return {'channel': channel, 'users': [source], 'modes': self.channels[channel].modes}
|
return {'channel': channel, 'users': [source], 'modes': self._channels[channel].modes}
|
||||||
|
|
||||||
def handle_kick(self, source, command, args):
|
def handle_kick(self, source, command, args):
|
||||||
"""
|
"""
|
||||||
@ -891,7 +891,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
del self.kick_queue[channel]
|
del self.kick_queue[channel]
|
||||||
|
|
||||||
# Statekeeping: remove the target from the channel they were previously in.
|
# Statekeeping: remove the target from the channel they were previously in.
|
||||||
self.channels[channel].remove_user(target)
|
self._channels[channel].remove_user(target)
|
||||||
try:
|
try:
|
||||||
self.users[target].channels.remove(channel)
|
self.users[target].channels.remove(channel)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -903,7 +903,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
|
|
||||||
# Delete channels that we were kicked from, for better state keeping.
|
# Delete channels that we were kicked from, for better state keeping.
|
||||||
if self.pseudoclient and target == self.pseudoclient.uid:
|
if self.pseudoclient and target == self.pseudoclient.uid:
|
||||||
del self.channels[channel]
|
del self._channels[channel]
|
||||||
|
|
||||||
def handle_mode(self, source, command, args):
|
def handle_mode(self, source, command, args):
|
||||||
"""Handles MODE changes."""
|
"""Handles MODE changes."""
|
||||||
@ -911,7 +911,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
# <- :ice MODE ice :+Zi
|
# <- :ice MODE ice :+Zi
|
||||||
target = args[0]
|
target = args[0]
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
oldobj = self.channels[target].deepcopy()
|
oldobj = self._channels[target].deepcopy()
|
||||||
else:
|
else:
|
||||||
target = self.nick_to_uid(target)
|
target = self.nick_to_uid(target)
|
||||||
oldobj = None
|
oldobj = None
|
||||||
@ -944,7 +944,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
"""Handles TS announcements via RPL_CREATIONTIME."""
|
"""Handles TS announcements via RPL_CREATIONTIME."""
|
||||||
channel = args[1]
|
channel = args[1]
|
||||||
ts = int(args[2])
|
ts = int(args[2])
|
||||||
self.channels[channel].ts = ts
|
self._channels[channel].ts = ts
|
||||||
|
|
||||||
def handle_chghost(self, source, command, args):
|
def handle_chghost(self, source, command, args):
|
||||||
"""Handles the IRCv3 CHGHOST command."""
|
"""Handles the IRCv3 CHGHOST command."""
|
||||||
@ -995,15 +995,15 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
|
|||||||
reason = ''
|
reason = ''
|
||||||
|
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
self.channels[channel].remove_user(source)
|
self._channels[channel].remove_user(source)
|
||||||
self.users[source].channels -= set(channels)
|
self.users[source].channels -= set(channels)
|
||||||
|
|
||||||
self.call_hooks([source, 'PART', {'channels': channels, 'text': reason}])
|
self.call_hooks([source, 'PART', {'channels': channels, 'text': reason}])
|
||||||
|
|
||||||
# Clear channels that are empty, or that we're parting.
|
# Clear channels that are empty, or that we're parting.
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
if (self.pseudoclient and source == self.pseudoclient.uid) or not self.channels[channel].users:
|
if (self.pseudoclient and source == self.pseudoclient.uid) or not self._channels[channel].users:
|
||||||
del self.channels[channel]
|
del self._channels[channel]
|
||||||
|
|
||||||
def handle_ping(self, source, command, args):
|
def handle_ping(self, source, command, args):
|
||||||
"""
|
"""
|
||||||
|
@ -162,12 +162,12 @@ class HybridProtocol(TS6Protocol):
|
|||||||
if not self.is_internal_server(numeric):
|
if not self.is_internal_server(numeric):
|
||||||
raise LookupError('No such PyLink server exists.')
|
raise LookupError('No such PyLink server exists.')
|
||||||
|
|
||||||
ts = self.channels[target].ts
|
ts = self._channels[target].ts
|
||||||
servername = self.servers[numeric].name
|
servername = self.servers[numeric].name
|
||||||
|
|
||||||
self._send_with_prefix(numeric, 'TBURST %s %s %s %s :%s' % (ts, target, int(time.time()), servername, text))
|
self._send_with_prefix(numeric, 'TBURST %s %s %s %s :%s' % (ts, target, int(time.time()), servername, text))
|
||||||
self.channels[target].topic = text
|
self._channels[target].topic = text
|
||||||
self.channels[target].topicset = True
|
self._channels[target].topicset = True
|
||||||
|
|
||||||
# command handlers
|
# command handlers
|
||||||
|
|
||||||
@ -221,8 +221,8 @@ class HybridProtocol(TS6Protocol):
|
|||||||
ts = args[2]
|
ts = args[2]
|
||||||
setter = args[3]
|
setter = args[3]
|
||||||
topic = args[-1]
|
topic = args[-1]
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
||||||
|
|
||||||
def handle_eob(self, numeric, command, args):
|
def handle_eob(self, numeric, command, args):
|
||||||
|
@ -83,11 +83,11 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
|
|
||||||
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
|
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
|
||||||
modes = [m for m in self.channels[channel].modes if m[0] not in self.cmodes['*A']]
|
modes = [m for m in self._channels[channel].modes if m[0] not in self.cmodes['*A']]
|
||||||
self._send_with_prefix(server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
|
self._send_with_prefix(server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
|
||||||
ts=self.channels[channel].ts, uid=client, channel=channel,
|
ts=self._channels[channel].ts, uid=client, channel=channel,
|
||||||
modes=self.join_modes(modes)))
|
modes=self.join_modes(modes)))
|
||||||
self.channels[channel].users.add(client)
|
self._channels[channel].users.add(client)
|
||||||
self.users[client].channels.add(channel)
|
self.users[client].channels.add(channel)
|
||||||
|
|
||||||
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
||||||
@ -109,8 +109,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
|
|
||||||
# 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 = modes or self.channels[channel].modes
|
modes = modes or self._channels[channel].modes
|
||||||
orig_ts = self.channels[channel].ts
|
orig_ts = self._channels[channel].ts
|
||||||
ts = ts or orig_ts
|
ts = ts or orig_ts
|
||||||
|
|
||||||
banmodes = []
|
banmodes = []
|
||||||
@ -120,7 +120,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
if modechar in self.cmodes['*A']:
|
if modechar in self.cmodes['*A']:
|
||||||
# Track bans separately (they are sent as a normal FMODE instead of in FJOIN.
|
# Track bans separately (they are sent as a normal FMODE instead of in FJOIN.
|
||||||
# However, don't reset bans that have already been set.
|
# However, don't reset bans that have already been set.
|
||||||
if (modechar, mode[1]) not in self.channels[channel].modes:
|
if (modechar, mode[1]) not in self._channels[channel].modes:
|
||||||
banmodes.append(mode)
|
banmodes.append(mode)
|
||||||
else:
|
else:
|
||||||
regularmodes.append(mode)
|
regularmodes.append(mode)
|
||||||
@ -146,7 +146,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
self._send_with_prefix(server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
self._send_with_prefix(server, "FJOIN {channel} {ts} {modes} :{users}".format(
|
||||||
ts=ts, users=namelist, channel=channel,
|
ts=ts, users=namelist, channel=channel,
|
||||||
modes=self.join_modes(modes)))
|
modes=self.join_modes(modes)))
|
||||||
self.channels[channel].users.update(uids)
|
self._channels[channel].users.update(uids)
|
||||||
|
|
||||||
if banmodes:
|
if banmodes:
|
||||||
# Burst ban modes if there are any.
|
# Burst ban modes if there are any.
|
||||||
@ -207,7 +207,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
self.apply_modes(target, modes)
|
self.apply_modes(target, modes)
|
||||||
joinedmodes = self.join_modes(modes)
|
joinedmodes = self.join_modes(modes)
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
ts = ts or self.channels[target].ts
|
ts = ts or self._channels[target].ts
|
||||||
self._send_with_prefix(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
|
self._send_with_prefix(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
|
||||||
else:
|
else:
|
||||||
self._send_with_prefix(numeric, 'MODE %s %s' % (target, joinedmodes))
|
self._send_with_prefix(numeric, 'MODE %s %s' % (target, joinedmodes))
|
||||||
@ -241,8 +241,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
ts = int(time.time())
|
ts = int(time.time())
|
||||||
servername = self.servers[numeric].name
|
servername = self.servers[numeric].name
|
||||||
self._send_with_prefix(numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
|
self._send_with_prefix(numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
|
||||||
self.channels[target].topic = text
|
self._channels[target].topic = text
|
||||||
self.channels[target].topicset = True
|
self._channels[target].topicset = True
|
||||||
|
|
||||||
def knock(self, numeric, target, text):
|
def knock(self, numeric, target, text):
|
||||||
"""Sends a KNOCK from a PyLink client."""
|
"""Sends a KNOCK from a PyLink client."""
|
||||||
@ -539,7 +539,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
"""Handles incoming FJOIN commands (InspIRCd equivalent of JOIN/SJOIN)."""
|
"""Handles incoming FJOIN commands (InspIRCd equivalent of JOIN/SJOIN)."""
|
||||||
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...>
|
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...>
|
||||||
channel = args[0]
|
channel = args[0]
|
||||||
chandata = self.channels[channel].deepcopy()
|
chandata = self._channels[channel].deepcopy()
|
||||||
# InspIRCd sends each channel's users in the form of 'modeprefix(es),UID'
|
# InspIRCd sends each channel's users in the form of 'modeprefix(es),UID'
|
||||||
userlist = args[-1].split()
|
userlist = args[-1].split()
|
||||||
|
|
||||||
@ -565,7 +565,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# Only save mode changes if the remote has lower TS than us.
|
# Only save mode changes if the remote has lower TS than us.
|
||||||
changedmodes |= {('+%s' % mode, user) for mode in modeprefix}
|
changedmodes |= {('+%s' % mode, user) for mode in modeprefix}
|
||||||
|
|
||||||
self.channels[channel].users.add(user)
|
self._channels[channel].users.add(user)
|
||||||
|
|
||||||
# Statekeeping with timestamps. Note: some service packages (Anope 1.8) send a trailing
|
# Statekeeping with timestamps. Note: some service packages (Anope 1.8) send a trailing
|
||||||
# 'd' after the timestamp, which we should strip out to prevent int() from erroring.
|
# 'd' after the timestamp, which we should strip out to prevent int() from erroring.
|
||||||
@ -574,7 +574,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
# <- :3AX FJOIN #monitor 1485462109d + :,3AXAAAAAK
|
# <- :3AX FJOIN #monitor 1485462109d + :,3AXAAAAAK
|
||||||
their_ts = int(''.join(char for char in args[1] if char.isdigit()))
|
their_ts = int(''.join(char for char in args[1] if char.isdigit()))
|
||||||
|
|
||||||
our_ts = self.channels[channel].ts
|
our_ts = self._channels[channel].ts
|
||||||
self.updateTS(servernumeric, channel, their_ts, changedmodes)
|
self.updateTS(servernumeric, channel, their_ts, changedmodes)
|
||||||
|
|
||||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
|
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
|
||||||
@ -627,7 +627,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
"""Handles the FMODE command, used for channel mode changes."""
|
"""Handles the FMODE command, used for channel mode changes."""
|
||||||
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
|
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
|
||||||
channel = args[0]
|
channel = args[0]
|
||||||
oldobj = self.channels[channel].deepcopy()
|
oldobj = self._channels[channel].deepcopy()
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
changedmodes = self.parse_modes(channel, modes)
|
changedmodes = self.parse_modes(channel, modes)
|
||||||
self.apply_modes(channel, changedmodes)
|
self.apply_modes(channel, changedmodes)
|
||||||
@ -661,8 +661,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
|
|||||||
ts = args[1]
|
ts = args[1]
|
||||||
setter = args[2]
|
setter = args[2]
|
||||||
topic = args[-1]
|
topic = args[-1]
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
||||||
|
|
||||||
# SVSTOPIC is used by InspIRCd module m_topiclock - its arguments are the same as FTOPIC
|
# SVSTOPIC is used by InspIRCd module m_topiclock - its arguments are the same as FTOPIC
|
||||||
|
@ -110,7 +110,7 @@ class IRCCommonProtocol(IRCNetwork):
|
|||||||
|
|
||||||
# Prevent RuntimeError: dictionary changed size during iteration
|
# Prevent RuntimeError: dictionary changed size during iteration
|
||||||
old_servers = self.servers.copy()
|
old_servers = self.servers.copy()
|
||||||
old_channels = self.channels.copy()
|
old_channels = self._channels.copy()
|
||||||
|
|
||||||
# Cycle through our list of servers. If any server's uplink is the one that is being SQUIT,
|
# Cycle through our list of servers. If any server's uplink is the one that is being SQUIT,
|
||||||
# remove them and all their users too.
|
# remove them and all their users too.
|
||||||
@ -468,8 +468,8 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
|||||||
raise LookupError('No such PyLink client/server exists.')
|
raise LookupError('No such PyLink client/server exists.')
|
||||||
|
|
||||||
self._send_with_prefix(source, 'TOPIC %s :%s' % (target, text))
|
self._send_with_prefix(source, 'TOPIC %s :%s' % (target, text))
|
||||||
self.channels[target].topic = text
|
self._channels[target].topic = text
|
||||||
self.channels[target].topicset = True
|
self._channels[target].topicset = True
|
||||||
topic_burst = topic
|
topic_burst = topic
|
||||||
|
|
||||||
def handle_invite(self, numeric, command, args):
|
def handle_invite(self, numeric, command, args):
|
||||||
@ -590,7 +590,7 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
|||||||
# <- ABAAA OM #test +h ABAAA
|
# <- ABAAA OM #test +h ABAAA
|
||||||
target = self._get_UID(args[0])
|
target = self._get_UID(args[0])
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
channeldata = self.channels[target].deepcopy()
|
channeldata = self._channels[target].deepcopy()
|
||||||
else:
|
else:
|
||||||
channeldata = None
|
channeldata = None
|
||||||
|
|
||||||
@ -611,11 +611,11 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
|||||||
channels = args[0].split(',')
|
channels = args[0].split(',')
|
||||||
|
|
||||||
for channel in channels.copy():
|
for channel in channels.copy():
|
||||||
if channel not in self.channels or source not in self.channels[channel].users:
|
if channel not in self._channels or source not in self._channels[channel].users:
|
||||||
# Ignore channels the user isn't on, and remove them from any hook payloads.
|
# Ignore channels the user isn't on, and remove them from any hook payloads.
|
||||||
channels.remove(channel)
|
channels.remove(channel)
|
||||||
|
|
||||||
self.channels[channel].remove_user(source)
|
self._channels[channel].remove_user(source)
|
||||||
try:
|
try:
|
||||||
self.users[source].channels.discard(channel)
|
self.users[source].channels.discard(channel)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -627,8 +627,8 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
|||||||
reason = ''
|
reason = ''
|
||||||
|
|
||||||
# Clear empty non-permanent channels.
|
# Clear empty non-permanent channels.
|
||||||
if not (self.channels[channel].users or ((self.cmodes.get('permanent'), None) in self.channels[channel].modes)):
|
if not (self._channels[channel].users or ((self.cmodes.get('permanent'), None) in self._channels[channel].modes)):
|
||||||
del self.channels[channel]
|
del self._channels[channel]
|
||||||
|
|
||||||
if channels:
|
if channels:
|
||||||
return {'channels': channels, 'text': reason}
|
return {'channels': channels, 'text': reason}
|
||||||
@ -685,9 +685,9 @@ class IRCS2SProtocol(IRCCommonProtocol):
|
|||||||
channel = args[0]
|
channel = args[0]
|
||||||
topic = args[1]
|
topic = args[1]
|
||||||
|
|
||||||
oldtopic = self.channels[channel].topic
|
oldtopic = self._channels[channel].topic
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
|
|
||||||
return {'channel': channel, 'setter': numeric, 'text': topic,
|
return {'channel': channel, 'setter': numeric, 'text': topic,
|
||||||
'oldtopic': oldtopic}
|
'oldtopic': oldtopic}
|
||||||
|
@ -154,7 +154,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
|||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
|
|
||||||
self._send_with_prefix(client, "JOIN %s" % channel)
|
self._send_with_prefix(client, "JOIN %s" % channel)
|
||||||
self.channels[channel].users.add(client)
|
self._channels[channel].users.add(client)
|
||||||
self.users[client].channels.add(channel)
|
self.users[client].channels.add(channel)
|
||||||
|
|
||||||
def kill(self, source, target, reason):
|
def kill(self, source, target, reason):
|
||||||
@ -241,7 +241,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
|||||||
# Add the affected users to our state.
|
# Add the affected users to our state.
|
||||||
for userpair in users:
|
for userpair in users:
|
||||||
uid = userpair[1]
|
uid = userpair[1]
|
||||||
self.channels[channel].users.add(uid)
|
self._channels[channel].users.add(uid)
|
||||||
try:
|
try:
|
||||||
self.users[uid].channels.add(channel)
|
self.users[uid].channels.add(channel)
|
||||||
except KeyError: # Not initialized yet?
|
except KeyError: # Not initialized yet?
|
||||||
@ -337,8 +337,8 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
|||||||
topic = args[-1]
|
topic = args[-1]
|
||||||
if topic:
|
if topic:
|
||||||
log.debug('(%s) handle_chaninfo: setting topic for %s to %r', self.name, channel, topic)
|
log.debug('(%s) handle_chaninfo: setting topic for %s to %r', self.name, channel, topic)
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
|
|
||||||
if len(args) >= 5:
|
if len(args) >= 5:
|
||||||
key = args[2]
|
key = args[2]
|
||||||
@ -364,10 +364,10 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
channel = chanpair
|
channel = chanpair
|
||||||
|
|
||||||
c = self.channels[channel]
|
c = self._channels[channel]
|
||||||
|
|
||||||
self.users[source].channels.add(channel)
|
self.users[source].channels.add(channel)
|
||||||
self.channels[channel].users.add(source)
|
self._channels[channel].users.add(source)
|
||||||
|
|
||||||
# Call hooks manually, because one JOIN command have multiple channels.
|
# Call hooks manually, because one JOIN command have multiple channels.
|
||||||
self.call_hooks([source, command, {'channel': channel, 'users': [source], 'modes': c.modes}])
|
self.call_hooks([source, command, {'channel': channel, 'users': [source], 'modes': c.modes}])
|
||||||
@ -469,7 +469,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
|||||||
# <- :ngircd.midnight.local NJOIN #test :tester,@%GL
|
# <- :ngircd.midnight.local NJOIN #test :tester,@%GL
|
||||||
|
|
||||||
channel = args[0]
|
channel = args[0]
|
||||||
chandata = self.channels[channel].deepcopy()
|
chandata = self._channels[channel].deepcopy()
|
||||||
namelist = []
|
namelist = []
|
||||||
|
|
||||||
# Reverse the modechar->modeprefix mapping for quicker lookup
|
# Reverse the modechar->modeprefix mapping for quicker lookup
|
||||||
@ -487,7 +487,7 @@ class NgIRCdProtocol(IRCS2SProtocol):
|
|||||||
|
|
||||||
# Final bits of state tracking. (I hate having to do this everywhere...)
|
# Final bits of state tracking. (I hate having to do this everywhere...)
|
||||||
self.users[user].channels.add(channel)
|
self.users[user].channels.add(channel)
|
||||||
self.channels[channel].users.add(user)
|
self._channels[channel].users.add(user)
|
||||||
|
|
||||||
return {'channel': channel, 'users': namelist, 'modes': [], 'channeldata': chandata}
|
return {'channel': channel, 'users': namelist, 'modes': [], 'channeldata': chandata}
|
||||||
|
|
||||||
|
@ -322,23 +322,23 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
|
|
||||||
nick = self.users[target].nick
|
nick = self.users[target].nick
|
||||||
|
|
||||||
self._send_with_prefix(numeric, 'I %s %s %s' % (nick, channel, self.channels[channel].ts))
|
self._send_with_prefix(numeric, 'I %s %s %s' % (nick, channel, self._channels[channel].ts))
|
||||||
|
|
||||||
def join(self, client, channel):
|
def join(self, client, channel):
|
||||||
"""Joins a PyLink client to a channel."""
|
"""Joins a PyLink client to a channel."""
|
||||||
# <- ABAAB J #test3 1460744371
|
# <- ABAAB J #test3 1460744371
|
||||||
ts = self.channels[channel].ts
|
ts = self._channels[channel].ts
|
||||||
|
|
||||||
if not self.is_internal_client(client):
|
if not self.is_internal_client(client):
|
||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
|
|
||||||
if not self.channels[channel].users:
|
if not self._channels[channel].users:
|
||||||
# Empty channels should be created with the CREATE command.
|
# Empty channels should be created with the CREATE command.
|
||||||
self._send_with_prefix(client, "C {channel} {ts}".format(ts=ts, channel=channel))
|
self._send_with_prefix(client, "C {channel} {ts}".format(ts=ts, channel=channel))
|
||||||
else:
|
else:
|
||||||
self._send_with_prefix(client, "J {channel} {ts}".format(ts=ts, channel=channel))
|
self._send_with_prefix(client, "J {channel} {ts}".format(ts=ts, channel=channel))
|
||||||
|
|
||||||
self.channels[channel].users.add(client)
|
self._channels[channel].users.add(client)
|
||||||
self.users[client].channels.add(channel)
|
self.users[client].channels.add(channel)
|
||||||
|
|
||||||
def kick(self, numeric, channel, target, reason=None):
|
def kick(self, numeric, channel, target, reason=None):
|
||||||
@ -351,7 +351,7 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
if not reason:
|
if not reason:
|
||||||
reason = 'No reason given'
|
reason = 'No reason given'
|
||||||
|
|
||||||
cobj = self.channels[channel]
|
cobj = self._channels[channel]
|
||||||
# Prevent kick bounces by sending our kick through the server if
|
# Prevent kick bounces by sending our kick through the server if
|
||||||
# the sender isn't op.
|
# the sender isn't op.
|
||||||
if numeric not in self.servers and (not cobj.is_halfop_plus(numeric)):
|
if numeric not in self.servers and (not cobj.is_halfop_plus(numeric)):
|
||||||
@ -409,7 +409,7 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
is_cmode = utils.isChannel(target)
|
is_cmode = utils.isChannel(target)
|
||||||
if is_cmode:
|
if is_cmode:
|
||||||
# Channel mode changes have a trailing TS. User mode changes do not.
|
# Channel mode changes have a trailing TS. User mode changes do not.
|
||||||
cobj = self.channels[target]
|
cobj = self._channels[target]
|
||||||
ts = ts or cobj.ts
|
ts = ts or cobj.ts
|
||||||
|
|
||||||
# Prevent mode bounces by sending our mode through the server if
|
# Prevent mode bounces by sending our mode through the server if
|
||||||
@ -530,8 +530,8 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
# <- AB B #test 1460742014 +tnl 10 ABAAB,ABAAA:o :%*!*@other.bad.host *!*@bad.host
|
# <- AB B #test 1460742014 +tnl 10 ABAAB,ABAAA:o :%*!*@other.bad.host *!*@bad.host
|
||||||
# <- AB B #test2 1460743539 +l 10 ABAAA:vo :%*!*@bad.host
|
# <- AB B #test2 1460743539 +l 10 ABAAA:vo :%*!*@bad.host
|
||||||
# <- AB B #test 1460747615 ABAAA:o :% ~ *!*@test.host
|
# <- AB B #test 1460747615 ABAAA:o :% ~ *!*@test.host
|
||||||
modes = modes or self.channels[channel].modes
|
modes = modes or self._channels[channel].modes
|
||||||
orig_ts = self.channels[channel].ts
|
orig_ts = self._channels[channel].ts
|
||||||
ts = ts or orig_ts
|
ts = ts or orig_ts
|
||||||
|
|
||||||
bans = []
|
bans = []
|
||||||
@ -541,7 +541,7 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
modechar = mode[0][-1]
|
modechar = mode[0][-1]
|
||||||
# Store bans and exempts in separate lists for processing, but don't reset bans that have already been set.
|
# Store bans and exempts in separate lists for processing, but don't reset bans that have already been set.
|
||||||
if modechar in self.cmodes['*A']:
|
if modechar in self.cmodes['*A']:
|
||||||
if (modechar, mode[1]) not in self.channels[channel].modes:
|
if (modechar, mode[1]) not in self._channels[channel].modes:
|
||||||
if modechar == 'b':
|
if modechar == 'b':
|
||||||
bans.append(mode[1])
|
bans.append(mode[1])
|
||||||
elif modechar == 'e':
|
elif modechar == 'e':
|
||||||
@ -624,7 +624,7 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
|
|
||||||
self.send(wrapped_msg)
|
self.send(wrapped_msg)
|
||||||
|
|
||||||
self.channels[channel].users.update(changedusers)
|
self._channels[channel].users.update(changedusers)
|
||||||
|
|
||||||
# Technically we can send bans together with the above user introductions, but
|
# Technically we can send bans together with the above user introductions, but
|
||||||
# it's easier to line wrap them separately.
|
# it's easier to line wrap them separately.
|
||||||
@ -699,12 +699,12 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
# For servers, just show the server name.
|
# For servers, just show the server name.
|
||||||
sendername = self.get_friendly_name(source)
|
sendername = self.get_friendly_name(source)
|
||||||
|
|
||||||
creationts = self.channels[target].ts
|
creationts = self._channels[target].ts
|
||||||
|
|
||||||
self._send_with_prefix(source, 'T %s %s %s %s :%s' % (target, sendername, creationts,
|
self._send_with_prefix(source, 'T %s %s %s %s :%s' % (target, sendername, creationts,
|
||||||
int(time.time()), text))
|
int(time.time()), text))
|
||||||
self.channels[target].topic = text
|
self._channels[target].topic = text
|
||||||
self.channels[target].topicset = True
|
self._channels[target].topicset = True
|
||||||
topic_burst = topic
|
topic_burst = topic
|
||||||
|
|
||||||
def update_client(self, target, field, text):
|
def update_client(self, target, field, text):
|
||||||
@ -1004,7 +1004,7 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
return
|
return
|
||||||
|
|
||||||
channel = args[0]
|
channel = args[0]
|
||||||
chandata = self.channels[channel].deepcopy()
|
chandata = self._channels[channel].deepcopy()
|
||||||
|
|
||||||
bans = []
|
bans = []
|
||||||
if args[-1].startswith('%'):
|
if args[-1].startswith('%'):
|
||||||
@ -1068,11 +1068,11 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
# Only save mode changes if the remote has lower TS than us.
|
# Only save mode changes if the remote has lower TS than us.
|
||||||
changedmodes |= {('+%s' % mode, user) for mode in prefixes}
|
changedmodes |= {('+%s' % mode, user) for mode in prefixes}
|
||||||
|
|
||||||
self.channels[channel].users.add(user)
|
self._channels[channel].users.add(user)
|
||||||
|
|
||||||
# Statekeeping with timestamps
|
# Statekeeping with timestamps
|
||||||
their_ts = int(args[1])
|
their_ts = int(args[1])
|
||||||
our_ts = self.channels[channel].ts
|
our_ts = self._channels[channel].ts
|
||||||
self.updateTS(source, channel, their_ts, changedmodes)
|
self.updateTS(source, channel, their_ts, changedmodes)
|
||||||
|
|
||||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
|
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
|
||||||
@ -1096,7 +1096,7 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
self.name, source, oldchans)
|
self.name, source, oldchans)
|
||||||
|
|
||||||
for channel in oldchans:
|
for channel in oldchans:
|
||||||
self.channels[channel].users.discard(source)
|
self._channels[channel].users.discard(source)
|
||||||
self.users[source].channels.discard(channel)
|
self.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'}
|
||||||
@ -1106,10 +1106,10 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
self.updateTS(source, channel, ts)
|
self.updateTS(source, channel, ts)
|
||||||
|
|
||||||
self.users[source].channels.add(channel)
|
self.users[source].channels.add(channel)
|
||||||
self.channels[channel].users.add(source)
|
self._channels[channel].users.add(source)
|
||||||
|
|
||||||
return {'channel': channel, 'users': [source], 'modes':
|
return {'channel': channel, 'users': [source], 'modes':
|
||||||
self.channels[channel].modes, 'ts': ts or int(time.time())}
|
self._channels[channel].modes, 'ts': ts or int(time.time())}
|
||||||
|
|
||||||
handle_create = handle_join
|
handle_create = handle_join
|
||||||
def handle_end_of_burst(self, source, command, args):
|
def handle_end_of_burst(self, source, command, args):
|
||||||
@ -1140,9 +1140,9 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
channel = args[0]
|
channel = args[0]
|
||||||
topic = args[-1]
|
topic = args[-1]
|
||||||
|
|
||||||
oldtopic = self.channels[channel].topic
|
oldtopic = self._channels[channel].topic
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
|
|
||||||
return {'channel': channel, 'setter': args[1], 'text': topic,
|
return {'channel': channel, 'setter': args[1], 'text': topic,
|
||||||
'oldtopic': oldtopic}
|
'oldtopic': oldtopic}
|
||||||
@ -1154,14 +1154,14 @@ class P10Protocol(IRCS2SProtocol):
|
|||||||
modes = args[1]
|
modes = args[1]
|
||||||
|
|
||||||
# Enumerate a list of our existing modes, including prefix modes.
|
# Enumerate a list of our existing modes, including prefix modes.
|
||||||
existing = list(self.channels[channel].modes)
|
existing = list(self._channels[channel].modes)
|
||||||
for pmode, userlist in self.channels[channel].prefixmodes.items():
|
for pmode, userlist in self._channels[channel].prefixmodes.items():
|
||||||
# Expand the prefix modes lists to individual ('o', 'UID') mode pairs.
|
# Expand the prefix modes lists to individual ('o', 'UID') mode pairs.
|
||||||
modechar = self.cmodes.get(pmode)
|
modechar = self.cmodes.get(pmode)
|
||||||
existing += [(modechar, user) for user in userlist]
|
existing += [(modechar, user) for user in userlist]
|
||||||
|
|
||||||
# Back up the channel state.
|
# Back up the channel state.
|
||||||
oldobj = self.channels[channel].deepcopy()
|
oldobj = self._channels[channel].deepcopy()
|
||||||
|
|
||||||
changedmodes = []
|
changedmodes = []
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
if not self.is_internal_client(client):
|
if not self.is_internal_client(client):
|
||||||
log.error('(%s) Error trying to join %r to %r (no such client exists)', self.name, client, channel)
|
log.error('(%s) Error trying to join %r to %r (no such client exists)', self.name, client, channel)
|
||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
self._send_with_prefix(client, "JOIN {ts} {channel} +".format(ts=self.channels[channel].ts, channel=channel))
|
self._send_with_prefix(client, "JOIN {ts} {channel} +".format(ts=self._channels[channel].ts, channel=channel))
|
||||||
self.channels[channel].users.add(client)
|
self._channels[channel].users.add(client)
|
||||||
self.users[client].channels.add(channel)
|
self.users[client].channels.add(channel)
|
||||||
|
|
||||||
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
||||||
@ -106,8 +106,8 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
if not server:
|
if not server:
|
||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
|
|
||||||
modes = set(modes or self.channels[channel].modes)
|
modes = set(modes or self._channels[channel].modes)
|
||||||
orig_ts = self.channels[channel].ts
|
orig_ts = self._channels[channel].ts
|
||||||
ts = ts or orig_ts
|
ts = ts or orig_ts
|
||||||
|
|
||||||
# Get all the ban modes in a separate list. These are bursted using a separate BMASK
|
# Get all the ban modes in a separate list. These are bursted using a separate BMASK
|
||||||
@ -119,7 +119,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
modechar = mode[0][-1]
|
modechar = mode[0][-1]
|
||||||
if modechar in self.cmodes['*A']:
|
if modechar in self.cmodes['*A']:
|
||||||
# Mode character is one of 'beIq'
|
# Mode character is one of 'beIq'
|
||||||
if (modechar, mode[1]) in self.channels[channel].modes:
|
if (modechar, mode[1]) in self._channels[channel].modes:
|
||||||
# Don't reset modes that are already set.
|
# Don't reset modes that are already set.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
self._send_with_prefix(server, "SJOIN {ts} {channel} {modes} :{users}".format(
|
self._send_with_prefix(server, "SJOIN {ts} {channel} {modes} :{users}".format(
|
||||||
ts=ts, users=namelist, channel=channel,
|
ts=ts, users=namelist, channel=channel,
|
||||||
modes=self.join_modes(regularmodes)))
|
modes=self.join_modes(regularmodes)))
|
||||||
self.channels[channel].users.update(uids)
|
self._channels[channel].users.update(uids)
|
||||||
|
|
||||||
# Now, burst bans.
|
# Now, burst bans.
|
||||||
# <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
|
# <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
|
||||||
@ -183,7 +183,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
modes = list(modes)
|
modes = list(modes)
|
||||||
|
|
||||||
if utils.isChannel(target):
|
if utils.isChannel(target):
|
||||||
ts = ts or self.channels[target].ts
|
ts = ts or self._channels[target].ts
|
||||||
# TMODE:
|
# TMODE:
|
||||||
# parameters: channelTS, channel, cmode changes, opt. cmode parameters...
|
# parameters: channelTS, channel, cmode changes, opt. cmode parameters...
|
||||||
|
|
||||||
@ -207,17 +207,17 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
# source: server
|
# source: server
|
||||||
# propagation: broadcast
|
# propagation: broadcast
|
||||||
# parameters: channel, topicTS, opt. topic setter, topic
|
# parameters: channel, topicTS, opt. topic setter, topic
|
||||||
ts = self.channels[target].ts
|
ts = self._channels[target].ts
|
||||||
servername = self.servers[numeric].name
|
servername = self.servers[numeric].name
|
||||||
self._send_with_prefix(numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
|
self._send_with_prefix(numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
|
||||||
self.channels[target].topic = text
|
self._channels[target].topic = text
|
||||||
self.channels[target].topicset = True
|
self._channels[target].topicset = True
|
||||||
|
|
||||||
def invite(self, numeric, target, channel):
|
def invite(self, numeric, target, channel):
|
||||||
"""Sends an INVITE from a PyLink client.."""
|
"""Sends an INVITE from a PyLink client.."""
|
||||||
if not self.is_internal_client(numeric):
|
if not self.is_internal_client(numeric):
|
||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
self._send_with_prefix(numeric, 'INVITE %s %s %s' % (target, channel, self.channels[channel].ts))
|
self._send_with_prefix(numeric, 'INVITE %s %s %s' % (target, channel, self._channels[channel].ts))
|
||||||
|
|
||||||
def knock(self, numeric, target, text):
|
def knock(self, numeric, target, text):
|
||||||
"""Sends a KNOCK from a PyLink client."""
|
"""Sends a KNOCK from a PyLink client."""
|
||||||
@ -458,7 +458,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
|
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
|
||||||
# <- :0UY SJOIN 1451041566 #channel +nt :@0UYAAAAAB
|
# <- :0UY SJOIN 1451041566 #channel +nt :@0UYAAAAAB
|
||||||
channel = args[1]
|
channel = args[1]
|
||||||
chandata = self.channels[channel].deepcopy()
|
chandata = self._channels[channel].deepcopy()
|
||||||
userlist = args[-1].split()
|
userlist = args[-1].split()
|
||||||
|
|
||||||
modestring = args[2:-1] or args[2]
|
modestring = args[2:-1] or args[2]
|
||||||
@ -495,11 +495,11 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
|
|
||||||
# Only save mode changes if the remote has lower TS than us.
|
# Only save mode changes if the remote has lower TS than us.
|
||||||
changedmodes |= {('+%s' % mode, user) for mode in finalprefix}
|
changedmodes |= {('+%s' % mode, user) for mode in finalprefix}
|
||||||
self.channels[channel].users.add(user)
|
self._channels[channel].users.add(user)
|
||||||
|
|
||||||
# Statekeeping with timestamps
|
# Statekeeping with timestamps
|
||||||
their_ts = int(args[0])
|
their_ts = int(args[0])
|
||||||
our_ts = self.channels[channel].ts
|
our_ts = self._channels[channel].ts
|
||||||
self.updateTS(servernumeric, channel, their_ts, changedmodes)
|
self.updateTS(servernumeric, channel, their_ts, changedmodes)
|
||||||
|
|
||||||
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
|
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
|
||||||
@ -516,7 +516,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
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.name, numeric, oldchans)
|
self.name, numeric, oldchans)
|
||||||
for channel in oldchans:
|
for channel in oldchans:
|
||||||
self.channels[channel].users.discard(numeric)
|
self._channels[channel].users.discard(numeric)
|
||||||
self.users[numeric].channels.discard(channel)
|
self.users[numeric].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:
|
||||||
@ -524,12 +524,12 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
self.updateTS(numeric, channel, ts)
|
self.updateTS(numeric, channel, ts)
|
||||||
|
|
||||||
self.users[numeric].channels.add(channel)
|
self.users[numeric].channels.add(channel)
|
||||||
self.channels[channel].users.add(numeric)
|
self._channels[channel].users.add(numeric)
|
||||||
|
|
||||||
# We send users and modes here because SJOIN and JOIN both use one hook,
|
# We send users and modes here because SJOIN and JOIN both use one hook,
|
||||||
# for simplicity's sake (with plugins).
|
# for simplicity's sake (with plugins).
|
||||||
return {'channel': channel, 'users': [numeric], 'modes':
|
return {'channel': channel, 'users': [numeric], 'modes':
|
||||||
self.channels[channel].modes, 'ts': ts}
|
self._channels[channel].modes, 'ts': ts}
|
||||||
|
|
||||||
def handle_euid(self, numeric, command, args):
|
def handle_euid(self, numeric, command, args):
|
||||||
"""Handles incoming EUID commands (user introduction)."""
|
"""Handles incoming EUID commands (user introduction)."""
|
||||||
@ -610,7 +610,7 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
# <- :42XAAAAAB TMODE 1437450768 #test -c+lkC 3 agte4
|
# <- :42XAAAAAB TMODE 1437450768 #test -c+lkC 3 agte4
|
||||||
# <- :0UYAAAAAD TMODE 0 #a +h 0UYAAAAAD
|
# <- :0UYAAAAAD TMODE 0 #a +h 0UYAAAAAD
|
||||||
channel = args[1]
|
channel = args[1]
|
||||||
oldobj = self.channels[channel].deepcopy()
|
oldobj = self._channels[channel].deepcopy()
|
||||||
modes = args[2:]
|
modes = args[2:]
|
||||||
changedmodes = self.parse_modes(channel, modes)
|
changedmodes = self.parse_modes(channel, modes)
|
||||||
self.apply_modes(channel, changedmodes)
|
self.apply_modes(channel, changedmodes)
|
||||||
@ -625,8 +625,8 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
ts = args[1]
|
ts = args[1]
|
||||||
setter = args[2]
|
setter = args[2]
|
||||||
topic = args[-1]
|
topic = args[-1]
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
||||||
|
|
||||||
def handle_etb(self, numeric, command, args):
|
def handle_etb(self, numeric, command, args):
|
||||||
@ -637,8 +637,8 @@ class TS6Protocol(TS6BaseProtocol):
|
|||||||
ts = args[2]
|
ts = args[2]
|
||||||
setter = args[3]
|
setter = args[3]
|
||||||
topic = args[-1]
|
topic = args[-1]
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
|
||||||
|
|
||||||
def handle_chghost(self, numeric, command, args):
|
def handle_chghost(self, numeric, command, args):
|
||||||
|
@ -106,7 +106,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
if not self.is_internal_client(client):
|
if not self.is_internal_client(client):
|
||||||
raise LookupError('No such PyLink client exists.')
|
raise LookupError('No such PyLink client exists.')
|
||||||
self._send_with_prefix(client, "JOIN %s" % channel)
|
self._send_with_prefix(client, "JOIN %s" % channel)
|
||||||
self.channels[channel].users.add(client)
|
self._channels[channel].users.add(client)
|
||||||
self.users[client].channels.add(channel)
|
self.users[client].channels.add(channel)
|
||||||
|
|
||||||
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
def sjoin(self, server, channel, users, ts=None, modes=set()):
|
||||||
@ -126,8 +126,8 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
if not server:
|
if not server:
|
||||||
raise LookupError('No such PyLink server exists.')
|
raise LookupError('No such PyLink server exists.')
|
||||||
|
|
||||||
changedmodes = set(modes or self.channels[channel].modes)
|
changedmodes = set(modes or self._channels[channel].modes)
|
||||||
orig_ts = self.channels[channel].ts
|
orig_ts = self._channels[channel].ts
|
||||||
ts = ts or orig_ts
|
ts = ts or orig_ts
|
||||||
uids = []
|
uids = []
|
||||||
itemlist = []
|
itemlist = []
|
||||||
@ -158,7 +158,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
if modepair[0][-1] in self.cmodes['*A']:
|
if modepair[0][-1] in self.cmodes['*A']:
|
||||||
# Bans, exempts, invex get expanded to forms like "&*!*@some.host" in SJOIN.
|
# Bans, exempts, invex get expanded to forms like "&*!*@some.host" in SJOIN.
|
||||||
|
|
||||||
if (modepair[0][-1], modepair[1]) in self.channels[channel].modes:
|
if (modepair[0][-1], modepair[1]) in self._channels[channel].modes:
|
||||||
# Mode is already set; skip it.
|
# Mode is already set; skip it.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
for line in utils.wrapArguments(sjoin_prefix, itemlist, self.S2S_BUFSIZE):
|
for line in utils.wrapArguments(sjoin_prefix, itemlist, self.S2S_BUFSIZE):
|
||||||
self.send(line)
|
self.send(line)
|
||||||
|
|
||||||
self.channels[channel].users.update(uids)
|
self._channels[channel].users.update(uids)
|
||||||
|
|
||||||
self.updateTS(server, channel, ts, changedmodes)
|
self.updateTS(server, channel, ts, changedmodes)
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
modes[idx] = (mode[0], self._expandPUID(mode[1]))
|
modes[idx] = (mode[0], self._expandPUID(mode[1]))
|
||||||
|
|
||||||
# The MODE command is used for channel mode changes only
|
# The MODE command is used for channel mode changes only
|
||||||
ts = ts or self.channels[target].ts
|
ts = ts or self._channels[target].ts
|
||||||
|
|
||||||
# 7 characters for "MODE", the space between MODE and the target, the space between the
|
# 7 characters for "MODE", the space between MODE and the target, the space between the
|
||||||
# target and mode list, and the space between the mode list and TS.
|
# target and mode list, and the space between the mode list and TS.
|
||||||
@ -532,15 +532,15 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
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.name, numeric, oldchans)
|
self.name, numeric, oldchans)
|
||||||
for ch in oldchans:
|
for ch in oldchans:
|
||||||
self.channels[ch].users.discard(numeric)
|
self._channels[ch].users.discard(numeric)
|
||||||
self.users[numeric].channels.discard(ch)
|
self.users[numeric].channels.discard(ch)
|
||||||
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
|
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for channel in args[0].split(','):
|
for channel in args[0].split(','):
|
||||||
c = self.channels[channel]
|
c = self._channels[channel]
|
||||||
self.users[numeric].channels.add(channel)
|
self.users[numeric].channels.add(channel)
|
||||||
self.channels[channel].users.add(numeric)
|
self._channels[channel].users.add(numeric)
|
||||||
# Call hooks manually, because one JOIN command in UnrealIRCd can
|
# Call hooks manually, because one JOIN command in UnrealIRCd can
|
||||||
# have multiple channels...
|
# have multiple channels...
|
||||||
self.call_hooks([numeric, command, {'channel': channel, 'users': [numeric], 'modes':
|
self.call_hooks([numeric, command, {'channel': channel, 'users': [numeric], 'modes':
|
||||||
@ -551,7 +551,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
# <- :001 SJOIN 1444361345 #test :001AAAAAA @001AAAAAB +001AAAAAC
|
# <- :001 SJOIN 1444361345 #test :001AAAAAA @001AAAAAB +001AAAAAC
|
||||||
# <- :001 SJOIN 1483250129 #services +nt :+001OR9V02 @*~001DH6901 &*!*@test "*!*@blah.blah '*!*@yes.no
|
# <- :001 SJOIN 1483250129 #services +nt :+001OR9V02 @*~001DH6901 &*!*@test "*!*@blah.blah '*!*@yes.no
|
||||||
channel = args[1]
|
channel = args[1]
|
||||||
chandata = self.channels[channel].deepcopy()
|
chandata = self._channels[channel].deepcopy()
|
||||||
userlist = args[-1].split()
|
userlist = args[-1].split()
|
||||||
|
|
||||||
namelist = []
|
namelist = []
|
||||||
@ -614,9 +614,9 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
# Only merge the remote's prefix modes if their TS is smaller or equal to ours.
|
# Only merge the remote's prefix modes if their TS is smaller or equal to ours.
|
||||||
changedmodes |= {('+%s' % mode, user) for mode in finalprefix}
|
changedmodes |= {('+%s' % mode, user) for mode in finalprefix}
|
||||||
|
|
||||||
self.channels[channel].users.add(user)
|
self._channels[channel].users.add(user)
|
||||||
|
|
||||||
our_ts = self.channels[channel].ts
|
our_ts = self._channels[channel].ts
|
||||||
their_ts = int(args[0])
|
their_ts = int(args[0])
|
||||||
self.updateTS(numeric, channel, their_ts, changedmodes)
|
self.updateTS(numeric, channel, their_ts, changedmodes)
|
||||||
|
|
||||||
@ -677,7 +677,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
# Also, we need to get rid of that extra space following the +f argument. :|
|
# Also, we need to get rid of that extra space following the +f argument. :|
|
||||||
if utils.isChannel(args[0]):
|
if utils.isChannel(args[0]):
|
||||||
channel = args[0]
|
channel = args[0]
|
||||||
oldobj = self.channels[channel].deepcopy()
|
oldobj = self._channels[channel].deepcopy()
|
||||||
|
|
||||||
modes = [arg for arg in args[1:] if arg] # normalize whitespace
|
modes = [arg for arg in args[1:] if arg] # normalize whitespace
|
||||||
parsedmodes = self.parse_modes(channel, modes)
|
parsedmodes = self.parse_modes(channel, modes)
|
||||||
@ -688,7 +688,7 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
# attempt to set modes by us was rejected for some reason (usually due to
|
# attempt to set modes by us was rejected for some reason (usually due to
|
||||||
# timestamps). Drop the mode change to prevent mode floods.
|
# timestamps). Drop the mode change to prevent mode floods.
|
||||||
log.debug("(%s) Received mode bounce %s in channel %s! Our TS: %s",
|
log.debug("(%s) Received mode bounce %s in channel %s! Our TS: %s",
|
||||||
self.name, modes, channel, self.channels[channel].ts)
|
self.name, modes, channel, self._channels[channel].ts)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.apply_modes(channel, parsedmodes)
|
self.apply_modes(channel, parsedmodes)
|
||||||
@ -849,9 +849,9 @@ class UnrealProtocol(TS6BaseProtocol):
|
|||||||
setter = args[1]
|
setter = args[1]
|
||||||
ts = args[2]
|
ts = args[2]
|
||||||
|
|
||||||
oldtopic = self.channels[channel].topic
|
oldtopic = self._channels[channel].topic
|
||||||
self.channels[channel].topic = topic
|
self._channels[channel].topic = topic
|
||||||
self.channels[channel].topicset = True
|
self._channels[channel].topicset = True
|
||||||
|
|
||||||
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic,
|
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic,
|
||||||
'oldtopic': oldtopic}
|
'oldtopic': oldtopic}
|
||||||
|
Loading…
Reference in New Issue
Block a user