3
0
mirror of https://github.com/jlu5/PyLink.git synced 2025-02-20 07:20:59 +01:00

Merge remote-tracking branch 'origin/clientbot-fixes' into devel

This commit is contained in:
James Lu 2017-10-10 20:53:12 -07:00
commit 66c762b63f
2 changed files with 54 additions and 24 deletions

View File

@ -163,3 +163,29 @@ def handle_time(irc, source, command, args):
timestring = time.ctime() timestring = time.ctime()
irc.numeric(irc.sid, 391, source, '%s :%s' % (irc.hostname(), timestring)) irc.numeric(irc.sid, 391, source, '%s :%s' % (irc.hostname(), timestring))
utils.add_hook(handle_time, 'TIME') utils.add_hook(handle_time, 'TIME')
def _state_cleanup_core(irc, source, channel):
"""
Handles PART and KICK on clientbot-like networks (where only the users and channels we see are available)
by deleting channels when we leave and users when they leave all shared channels.
"""
if irc.has_cap('visible-state-only'):
# Delete channels that we were removed from.
if irc.pseudoclient and source == irc.pseudoclient.uid:
log.debug('(%s) state_cleanup: removing channel %s since we have left', irc.name, channel)
del irc._channels[channel]
# Delete users no longer sharing a channel with us.
if not irc.users[source].channels:
log.debug('(%s) state_cleanup: removing user %s/%s who no longer shares a channel with us',
irc.name, source, irc.users[source].nick)
irc._remove_client(source)
def stats_cleanup_part(irc, source, command, args):
for channel in args['channels']:
_state_cleanup_core(irc, source, channel)
utils.add_hook(stats_cleanup_part, 'PART', priority=-100)
def stats_cleanup_kick(irc, source, command, args):
_state_cleanup_core(irc, args['target'], args['channel'])
utils.add_hook(stats_cleanup_kick, 'KICK', priority=-100)

View File

@ -25,7 +25,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.protocol_caps = {'clear-channels-on-leave', 'slash-in-nicks', 'slash-in-hosts', 'underscore-in-hosts'} self.protocol_caps = {'visible-state-only', 'slash-in-nicks', 'slash-in-hosts', 'underscore-in-hosts'}
self.has_eob = False self.has_eob = False
@ -253,6 +253,13 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self.send('NICK :%s' % newnick) self.send('NICK :%s' % newnick)
# No state update here: the IRCd will respond with a NICK acknowledgement if the change succeeds. # No state update here: the IRCd will respond with a NICK acknowledgement if the change succeeds.
else: else:
assert source, "No source given?"
# Check that the new nick exists and isn't the same client as the sender
# (for changing nick case)
nick_uid = self.nick_to_uid(newnick)
if nick_uid and nick_uid != source:
log.warning('(%s) Blocking attempt from virtual client %s to change nick to %s (nick in use)', self.name, source, newnick)
return
self.call_hooks([source, 'CLIENTBOT_NICK', {'newnick': newnick}]) self.call_hooks([source, 'CLIENTBOT_NICK', {'newnick': newnick}])
self.users[source].nick = newnick self.users[source].nick = newnick
@ -355,20 +362,22 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
Limited (internal) nick collision checking is done here to prevent Clientbot users from Limited (internal) nick collision checking is done here to prevent Clientbot users from
being confused with virtual clients, and vice versa.""" being confused with virtual clients, and vice versa."""
self._check_puid_collision(nick) self._check_puid_collision(nick)
idsource = self.nick_to_uid(nick) idsource = self.nick_to_uid(nick)
is_internal = self.is_internal_client(idsource)
# If this sender isn't known or it is one of our virtual clients, spawn a new one. if self.is_internal_client(idsource) and self.pseudoclient and idsource != self.pseudoclient.uid:
# This also takes care of any nick collisions caused by new, Clientbot users # We got a message from a client with the same nick as an internal client.
# taking the same nick as one of our virtual clients, and will force the virtual client to lose. # Fire a virtual nick collision to prevent mixing senders.
if (not idsource) or (is_internal and self.pseudoclient and idsource != self.pseudoclient.uid):
if idsource:
log.debug('(%s) Nick-colliding virtual client %s/%s', self.name, idsource, nick) log.debug('(%s) Nick-colliding virtual client %s/%s', self.name, idsource, nick)
self.call_hooks([self.sid, 'CLIENTBOT_NICKCOLLIDE', {'target': idsource, 'parse_as': 'SAVE'}]) self.call_hooks([self.sid, 'SAVE', {'target': idsource}])
# Clear the UID for this nick and spawn a new client for the nick that was just freed.
idsource = None
if idsource is None:
# If this sender doesn't already exist, spawn a new client.
idsource = self.spawn_client(nick, ident or 'unknown', host or 'unknown', idsource = self.spawn_client(nick, ident or 'unknown', host or 'unknown',
server=self.uplink, realname=FALLBACK_REALNAME).uid server=self.uplink, realname=FALLBACK_REALNAME).uid
return idsource return idsource
def parse_message_tags(self, data): def parse_message_tags(self, data):
@ -907,11 +916,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
if (not self.is_internal_client(source)) and not self.is_internal_server(source): if (not self.is_internal_client(source)) and not self.is_internal_server(source):
# Don't repeat hooks if we're the kicker. # Don't repeat hooks if we're the kicker.
self.call_hooks([source, 'KICK', {'channel': channel, 'target': target, 'text': reason}]) return {'channel': channel, 'target': target, 'text': reason}
# Delete channels that we were kicked from, for better state keeping.
if self.pseudoclient and target == self.pseudoclient.uid:
del self._channels[channel]
def handle_mode(self, source, command, args): def handle_mode(self, source, command, args):
"""Handles MODE changes.""" """Handles MODE changes."""
@ -975,21 +980,25 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
def handle_nick(self, source, command, args): def handle_nick(self, source, command, args):
"""Handles NICK changes.""" """Handles NICK changes."""
# <- :GL|!~GL@127.0.0.1 NICK :GL_ # <- :GL|!~GL@127.0.0.1 NICK :GL_
newnick = args[0]
if not self.connected.is_set(): if not self.connected.is_set():
# We haven't properly logged on yet, so any initial NICK should be treated as a forced # We haven't properly logged on yet, so any initial NICK should be treated as a forced
# nick change for us. For example, this clause is used to handle forced nick changes # nick change for us. For example, this clause is used to handle forced nick changes
# sent by ZNC, when the login nick and the actual IRC nick of the bouncer differ. # sent by ZNC, when the login nick and the actual IRC nick of the bouncer differ.
self.pseudoclient.nick = args[0] self.pseudoclient.nick = newnick
log.debug('(%s) Pre-auth FNC: Changing our nick to %s', self.name, args[0]) log.debug('(%s) Pre-auth FNC: Changing our nick to %s', self.name, args[0])
return return
elif source == self.pseudoclient.uid: elif source == self.pseudoclient.uid:
self._nick_fails = 0 # Our last nick change succeeded. self._nick_fails = 0 # Our last nick change succeeded.
oldnick = self.users[source].nick oldnick = self.users[source].nick
self.users[source].nick = args[0]
return {'newnick': args[0], 'oldnick': oldnick} # Check for any nick collisions with existing virtual clients.
self._check_nick_collision(newnick)
self.users[source].nick = newnick
return {'newnick': newnick, 'oldnick': oldnick}
def handle_part(self, source, command, args): def handle_part(self, source, command, args):
""" """
@ -1006,12 +1015,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
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}]) return {'channels': channels, 'text': reason}
# Clear channels that are empty, or that we're parting.
for channel in channels:
if (self.pseudoclient and source == self.pseudoclient.uid) or not self._channels[channel].users:
del self._channels[channel]
def handle_ping(self, source, command, args): def handle_ping(self, source, command, args):
""" """