3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-23 19:19:31 +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()
irc.numeric(irc.sid, 391, source, '%s :%s' % (irc.hostname(), timestring))
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):
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
@ -253,6 +253,13 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self.send('NICK :%s' % newnick)
# No state update here: the IRCd will respond with a NICK acknowledgement if the change succeeds.
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.users[source].nick = newnick
@ -355,20 +362,22 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
Limited (internal) nick collision checking is done here to prevent Clientbot users from
being confused with virtual clients, and vice versa."""
self._check_puid_collision(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.
# This also takes care of any nick collisions caused by new, Clientbot users
# taking the same nick as one of our virtual clients, and will force the virtual client to lose.
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)
self.call_hooks([self.sid, 'CLIENTBOT_NICKCOLLIDE', {'target': idsource, 'parse_as': 'SAVE'}])
if self.is_internal_client(idsource) and self.pseudoclient and idsource != self.pseudoclient.uid:
# We got a message from a client with the same nick as an internal client.
# Fire a virtual nick collision to prevent mixing senders.
log.debug('(%s) Nick-colliding virtual client %s/%s', self.name, idsource, nick)
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',
server=self.uplink, realname=FALLBACK_REALNAME).uid
return idsource
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):
# Don't repeat hooks if we're the kicker.
self.call_hooks([source, 'KICK', {'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]
return {'channel': channel, 'target': target, 'text': reason}
def handle_mode(self, source, command, args):
"""Handles MODE changes."""
@ -975,21 +980,25 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
def handle_nick(self, source, command, args):
"""Handles NICK changes."""
# <- :GL|!~GL@127.0.0.1 NICK :GL_
newnick = args[0]
if not self.connected.is_set():
# 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
# 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])
return
elif source == self.pseudoclient.uid:
self._nick_fails = 0 # Our last nick change succeeded.
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):
"""
@ -1006,12 +1015,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self._channels[channel].remove_user(source)
self.users[source].channels -= set(channels)
self.call_hooks([source, 'PART', {'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]
return {'channels': channels, 'text': reason}
def handle_ping(self, source, command, args):
"""