From e9fe15bd7d037343bd617ac576db99439629f444 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 4 May 2018 22:37:25 -0700 Subject: [PATCH 01/11] [WIP] Further revise the persistent channels implementation - Make dynamic_channels per plugin as well as per network to work around relay-service conflicts (#265) - Introduce ServiceBot.(add|remove)_persistent_channel() to add/remove persistent channels and optionally join/part them - Introduce ServiceBot.part(), which checks remaining persistent channels list and parts a channel only if it is still not marked persistent - Refactor automode to autojoin channels on ENDBURST instead of plugin load - Refactor relay to manage persistent channels on join/part/(de)init, both locally (namespace 'relay_local') and remotely (namespace 'relay_remote') --- plugins/automode.py | 20 +++--- plugins/relay.py | 37 ++++++++--- utils.py | 147 +++++++++++++++++++++++++++++++------------- 3 files changed, 146 insertions(+), 58 deletions(-) diff --git a/plugins/automode.py b/plugins/automode.py index 5cce99a..d1d329c 100644 --- a/plugins/automode.py +++ b/plugins/automode.py @@ -35,13 +35,6 @@ def main(irc=None): # Register our permissions. permissions.add_default_permissions(default_permissions) - # Queue joins to all channels where Automode has entries. - for entry in db: - netname, channel = entry.split('#', 1) - channel = '#' + channel - log.debug('automode: auto-joining %s on %s', channel, netname) - modebot.join(netname, channel, ignore_empty=True) - def die(irc=None): """Saves the Automode database and quit.""" datastore.die() @@ -120,6 +113,19 @@ def match(irc, channel, uids=None): irc.call_hooks([modebot_uid, 'AUTOMODE_MODE', {'target': channel, 'modes': outgoing_modes, 'parse_as': 'MODE'}]) +def handle_endburst(irc, source, command, args): + """ENDBURST hook handler - used to join the Automode service to channels where it has entries.""" + if source != irc.uplink: + return + + for entry in db: + netname, channel = entry.split('#', 1) + channel = '#' + channel + if netname == irc.name: + modebot.add_persistent_channel(irc, 'automode', channel) + +utils.add_hook(handle_endburst, 'ENDBURST') + def handle_join(irc, source, command, args): """ Automode JOIN listener. This sets modes accordingly if the person joining matches a mask in the diff --git a/plugins/relay.py b/plugins/relay.py index ddadcec..647e031 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -532,14 +532,14 @@ def initialize_channel(irc, channel): relay_joins(irc, channel, c.users, c.ts) if 'pylink' in world.services: - world.services['pylink'].join(irc, channel) + world.services['pylink'].add_persistent_channel(irc, 'relay_local', channel) def remove_channel(irc, channel): """Destroys a relay channel by parting all of its users.""" if irc is None: return - world.services['pylink'].dynamic_channels[irc.name].discard(channel) + world.services['pylink'].remove_persistent_channel(channel, 'relay_local', try_part=False) relay = get_relay(irc, channel) if relay and channel in irc.channels: @@ -549,13 +549,18 @@ def remove_channel(irc, channel): relay_part(irc, channel, user) else: # Part and quit all relay clients. - if irc.get_service_bot(user): # ...but ignore service bots - continue - irc.part(user, channel, 'Channel delinked.') - if user != irc.pseudoclient.uid and not irc.users[user].channels: - remoteuser = get_orig_user(irc, user) - del relayusers[remoteuser][irc.name] - irc.quit(user, 'Left all shared channels.') + # Service bots are treated differently: they have plugin-defined persistent + # channels, so we can request a part and it will apply if no other plugins + # have the channel registered. + sbot = irc.get_service_bot(user) + if sbot: + sbot.part(irc, channel) + else: + irc.part(user, channel, 'Channel delinked.') + if user != irc.pseudoclient.uid and not irc.users[user].channels: + remoteuser = get_orig_user(irc, user) + del relayusers[remoteuser][irc.name] + irc.quit(user, 'Left all shared channels.') def check_claim(irc, channel, sender, chanobj=None): """ @@ -734,6 +739,12 @@ def relay_joins(irc, channel, users, ts, targetirc=None, **kwargs): # proto.sjoin() takes its users as a list of (prefix mode characters, UID) pairs. userpair = (prefixes, u) + + # Join the service bot on the remote channel persistently. + rsbot = remoteirc.get_service_bot(u) + if rsbot: + rsbot.add_persistent_channel(irc, 'relay_remote', channel, try_join=False) + queued_users.append(userpair) if queued_users: @@ -806,6 +817,14 @@ def relay_part(irc, *args, **kwargs): # user doesn't exist, just do nothing. return + # Remove any persistent channel entries from the remote end. + rsbot = remoteirc.get_service_bot(u) + if rsbot: + try: + sbot.remove_persistent_channel(irc, 'relay_remote', channel, try_part=False) + except KeyError: + pass + # Part the relay client with the channel delinked message. remoteirc.part(remoteuser, remotechan, 'Channel delinked.') diff --git a/utils.py b/utils.py index 0c0277b..a108973 100644 --- a/utils.py +++ b/utils.py @@ -154,8 +154,9 @@ class ServiceBot(): # Track plugin-defined persistent channels. The bot will leave them if they're empty, # and rejoin whenever someone else does. - self.dynamic_channels = structures.KeyedDefaultdict(lambda netname: - structures.IRCCaseInsensitiveSet(world.networkobjects[netname])) + # This is stored as a nested dictionary: + # {"plugin1": {"net1": IRCCaseInsensitiveSet({"#a", "#b"}), "net2": ...}, ...} + self.dynamic_channels = {} # Service description, used in the default help command if one is given. self.desc = desc @@ -186,65 +187,93 @@ class ServiceBot(): def join(self, irc, channels, ignore_empty=True): """ - Joins the given service bot to the given channel(s). channels can be an iterable of channel names - or the name of a single channel (str). + Joins the given service bot to the given channel(s). "channels" can be + an iterable of channel names or the name of a single channel (str). - The ignore_empty option sets whether we should skip joining empty channels and join them - later when we see someone else join. This is option is disabled on networks where we cannot - monitor channel state. + The ignore_empty option sets whether we should skip joining empty + channels and join them later when we see someone else join. This is + option is disabled on networks where we cannot monitor channel state. """ + uid = self.uids.get(irc.name) + if uid is None: + return - if isinstance(irc, str): - netname = irc - else: - netname = irc.name - - # Ensure type safety: pluralize strings if only one channel was given, then convert to set. if isinstance(channels, str): channels = [channels] - channels = set(channels) - - # If the network was given as a string, look up the Irc object here. - try: - irc = world.networkobjects[netname] - except KeyError: - log.debug('(%s/%s) Skipping join(), IRC object not initialized yet', netname, self.name) - return if irc.has_cap('visible-state-only'): # Disable dynamic channel joining on networks where we can't monitor channels for joins. ignore_empty = False - try: - u = self.uids[irc.name] - except KeyError: - log.debug('(%s/%s) Skipping join(), UID not initialized yet', irc.name, self.name) - return - # Specify modes to join the services bot with. - joinmodes = irc.serverdata.get("%s_joinmodes" % self.name) or conf.conf.get(self.name, {}).get('joinmodes') or '' + joinmodes = irc.get_service_option(self.name, 'joinmodes', default='') joinmodes = ''.join([m for m in joinmodes if m in irc.prefixmodes]) - for chan in channels: - if irc.is_channel(chan): - if chan in irc.channels: - if u in irc.channels[chan].users: - log.debug('(%s) Skipping join of service %r to channel %r - it is already present', irc.name, self.name, chan) + for channel in channels: + if irc.is_channel(channel): + if channel in irc.channels: + if uid in irc.channels[channel].users: + log.debug('(%s/%s) Skipping join to %r - we are already present', irc.name, self.name, channel) continue elif ignore_empty: - log.debug('(%s) Skipping joining service %r to empty channel %r', irc.name, self.name, chan) + log.debug('(%s/%s) Skipping joining empty channel %r', irc.name, self.name, channel) continue - log.debug('(%s) Joining services %s to channel %s with modes %r', irc.name, self.name, chan, joinmodes) + log.debug('(%s/%s) Joining channel %s with modes %r', irc.name, self.name, channel, joinmodes) if joinmodes: # Modes on join were specified; use SJOIN to burst our service - irc.proto.sjoin(irc.sid, chan, [(joinmodes, u)]) + irc.proto.sjoin(irc.sid, channel, [(joinmodes, uid)]) else: - irc.proto.join(u, chan) + irc.proto.join(uid, channel) - irc.call_hooks([irc.sid, 'PYLINK_SERVICE_JOIN', {'channel': chan, 'users': [u]}]) + irc.call_hooks([irc.sid, 'PYLINK_SERVICE_JOIN', {'channel': channel, 'users': [uid]}]) else: - log.warning('(%s) Ignoring invalid autojoin channel %r.', irc.name, chan) + log.warning('(%s/%s) Ignoring invalid channel %r', irc.name, self.name, channel) + + def part(self, irc, channels): + """ + Parts the given service bot from the given channel(s) if no plugins + still register it as a persistent dynamic channel. + + "channels" can be an iterable of channel names or the name of a single + channel (str). + """ + uid = self.uids.get(irc.name) + if uid is None: + return + + if isinstance(channels, str): + channels = [channels] + + to_part = [] + persistent_channels = self.get_persistent_channels(irc) + for channel in channels: + ''' + if channel in irc.channels and uid in irc.channels[channel].users: + for dch_namespace, dch_data in self.dynamic_channels.items(): + if irc.name in dch_data and channel in dch_data[irc.name]: + log.debug('(%s/%s) Not parting %r because namespace %r still registers it ' + 'as a dynamic channel.', irc.name, self.name, channel, + dch_namespace) + break + else: + to_part.append(channel) + irc.part(uid, channel, '') # TODO: configurable part message? + ''' + + if channel in irc.channels and uid in irc.channels[channel].users: + if channel in persistent_channels: + log.debug('(%s/%s) Not parting %r because it is registered ' + 'as a dynamic channel: %r', irc.name, self.name, channel, + persistent_channels) + continue + to_part.append(channel) + irc.part(uid, channel, '') # TODO: configurable part message? + else: + log.debug('(%s/%s) Ignoring part to %r, we are not there', irc.name, self.name, channel) + continue + + irc.call_hooks([uid, 'PYLINK_SERVICE_PART', {'channels': to_part, 'text': ''}]) def reply(self, irc, text, notice=None, private=None): """Replies to a message as the service in question.""" @@ -399,11 +428,45 @@ class ServiceBot(): sbconf = conf.conf.get(self.name, {}) return irc.serverdata.get("%s_realname" % self.name) or sbconf.get('realname') or conf.conf['pylink'].get('realname') or self.name - def get_persistent_channels(self, irc): + def add_persistent_channel(self, irc, namespace, channel, try_join=True): """ - Returns a set of persistent channels for the IRC network. + Adds a persistent channel to the service bot on the given network and namespace. """ - channels = self.dynamic_channels[irc.name].copy() + namespace = self.dynamic_channels.setdefault(namespace, {}) + chanlist = namespace.setdefault(irc.name, structures.IRCCaseInsensitiveSet(irc)) + chanlist.add(channel) + + if try_join: + self.join(irc, [channel]) + + def remove_persistent_channel(self, irc, namespace, channel, try_part=True): + """ + Removes a persistent channel from the service bot on the given network and namespace. + """ + chanlist = self.dynamic_channels[namespace][irc.name].remove(channel) + + if try_part and irc.connected.is_set(): + self.part(irc, [channel]) + + def get_persistent_channels(self, irc, namespace=None): + """ + Returns a set of persistent channels for the IRC network, optionally filtering + by namespace is one is given. + """ + channels = structures.IRCCaseInsensitiveSet(irc) + if namespace: + chanlist = self.dynamic_channels.get(namespace, {}).get(irc.name, set()) + log.debug('(%s/%s) get_persistent_channels: adding channels ' + '%r from namespace %r (single)', irc.name, self.name, + chanlist, namespace) + channels |= chanlist + else: + for dch_namespace, dch_data in self.dynamic_channels.items(): + chanlist = dch_data.get(irc.name, set()) + log.debug('(%s/%s) get_persistent_channels: adding channels ' + '%r from namespace %r', irc.name, self.name, + chanlist, dch_namespace) + channels |= chanlist channels |= set(irc.serverdata.get(self.name+'_channels', [])) channels |= set(irc.serverdata.get('channels', [])) return channels From 8994811f54513ed4bec7d0148f3d0578e4bba0c7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 12:52:00 -0700 Subject: [PATCH 02/11] relay: further fixes for persistent channels Also, merge the 'relay_local' and 'relay_remote' namespaces into one. --- plugins/relay.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 647e031..8c55632 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -532,14 +532,14 @@ def initialize_channel(irc, channel): relay_joins(irc, channel, c.users, c.ts) if 'pylink' in world.services: - world.services['pylink'].add_persistent_channel(irc, 'relay_local', channel) + world.services['pylink'].add_persistent_channel(irc, 'relay', channel) def remove_channel(irc, channel): """Destroys a relay channel by parting all of its users.""" if irc is None: return - world.services['pylink'].remove_persistent_channel(channel, 'relay_local', try_part=False) + world.services['pylink'].remove_persistent_channel(irc, 'relay', channel, try_part=False) relay = get_relay(irc, channel) if relay and channel in irc.channels: @@ -720,6 +720,11 @@ def relay_joins(irc, channel, users, ts, targetirc=None, **kwargs): if not u: continue + # Join the service bot on the remote channel persistently. + rsbot = remoteirc.get_service_bot(u) + if rsbot: + rsbot.add_persistent_channel(irc, 'relay', channel, try_join=False) + if (remotechan not in remoteirc.channels) or u not in remoteirc.channels[remotechan].users: # Note: only join users if they aren't already joined. This prevents op floods # on charybdis from repeated SJOINs sent for one user. @@ -740,11 +745,6 @@ def relay_joins(irc, channel, users, ts, targetirc=None, **kwargs): # proto.sjoin() takes its users as a list of (prefix mode characters, UID) pairs. userpair = (prefixes, u) - # Join the service bot on the remote channel persistently. - rsbot = remoteirc.get_service_bot(u) - if rsbot: - rsbot.add_persistent_channel(irc, 'relay_remote', channel, try_join=False) - queued_users.append(userpair) if queued_users: @@ -818,10 +818,10 @@ def relay_part(irc, *args, **kwargs): return # Remove any persistent channel entries from the remote end. - rsbot = remoteirc.get_service_bot(u) + rsbot = remoteirc.get_service_bot(remoteuser) if rsbot: try: - sbot.remove_persistent_channel(irc, 'relay_remote', channel, try_part=False) + sbot.remove_persistent_channel(irc, 'relay', channel, try_part=False) except KeyError: pass From 92be421fad13409758724f08f907fdbb31f91d44 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 12:57:17 -0700 Subject: [PATCH 03/11] relay: attempt to remove persistent channels on delink as well Also, wrap remove_persistent_channel calls with a try/except when they may fail. --- plugins/relay.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 8c55632..95579c5 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -539,7 +539,10 @@ def remove_channel(irc, channel): if irc is None: return - world.services['pylink'].remove_persistent_channel(irc, 'relay', channel, try_part=False) + try: + world.services['pylink'].remove_persistent_channel(irc, 'relay', channel, try_part=False) + except KeyError: + log.warning('(%s) relay: failed to remove persistent channel %r on delink', irc.name, channel, exc_info=True) relay = get_relay(irc, channel) if relay and channel in irc.channels: @@ -554,7 +557,10 @@ def remove_channel(irc, channel): # have the channel registered. sbot = irc.get_service_bot(user) if sbot: - sbot.part(irc, channel) + try: + sbot.remove_persistent_channel(irc, 'relay', channel) + except KeyError: + pass else: irc.part(user, channel, 'Channel delinked.') if user != irc.pseudoclient.uid and not irc.users[user].channels: From 01c22aac216bb9e7de140779256e93dfb283e319 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 12:58:34 -0700 Subject: [PATCH 04/11] Revert "service_support: consistently rejoin all channels on kick and kill" This is no longer needed with plugin-specific persistent channels. This reverts commit 3c9dac7e6b2bfd20586a4fea9db40172e537344a. --- coremods/service_support.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/coremods/service_support.py b/coremods/service_support.py index f0e116e..92332fc 100644 --- a/coremods/service_support.py +++ b/coremods/service_support.py @@ -107,26 +107,17 @@ def handle_kill(irc, source, command, args): irc.pseudoclient = None userdata = args.get('userdata') sbot = irc.get_service_bot(target) - servicename = None - channels = [] - if userdata: - # Look for the target's service name - servicename = getattr(userdata, 'service', servicename) - channels = getattr(userdata, 'channels', channels) - elif sbot: - # Or its service bot instance + if userdata and hasattr(userdata, 'service'): # Look for the target's service name attribute + servicename = userdata.service + elif sbot: # Or their service bot instance servicename = sbot.name - channels = irc.users[target].channels if servicename: log.info('(%s) Received kill to service %r (nick: %r) from %s (reason: %r).', irc.name, servicename, userdata.nick if userdata else irc.users[target].nick, irc.get_hostmask(source), args.get('text')) spawn_service(irc, source, command, {'name': servicename}) - # Rejoin the killed service bot to all channels it was previously in. - world.services[servicename].join(irc, channels) - utils.add_hook(handle_kill, 'KILL') def handle_join(irc, source, command, args): @@ -173,7 +164,7 @@ def handle_kick(irc, source, command, args): if not _services_dynamic_part(irc, channel): kicked = args['target'] sbot = irc.get_service_bot(kicked) - if sbot: + if sbot and channel in sbot.get_persistent_channels(irc): sbot.join(irc, channel) utils.add_hook(handle_kick, 'KICK') From 253204250600bc0d58f60f7b69f717c58d2dfe94 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 13:05:43 -0700 Subject: [PATCH 05/11] services_support: raise endburst handler priority Other plugins may implement endburst handlers assuming that their service bots have already been created. --- coremods/service_support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coremods/service_support.py b/coremods/service_support.py index 92332fc..707f9fe 100644 --- a/coremods/service_support.py +++ b/coremods/service_support.py @@ -98,7 +98,7 @@ def handle_endburst(irc, source, command, args): for name, sbot in world.services.items(): spawn_service(irc, source, command, {'name': name}) -utils.add_hook(handle_endburst, 'ENDBURST') +utils.add_hook(handle_endburst, 'ENDBURST', priority=500) def handle_kill(irc, source, command, args): """Handle KILLs to PyLink service bots, respawning them as needed.""" From 61d7bf18d3797c5f7e61d614e4ec2c1221ed94da Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 13:15:17 -0700 Subject: [PATCH 06/11] relay: also attempt to part the PyLink service bot on delink --- plugins/relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 95579c5..cc225b7 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -540,7 +540,7 @@ def remove_channel(irc, channel): return try: - world.services['pylink'].remove_persistent_channel(irc, 'relay', channel, try_part=False) + world.services['pylink'].remove_persistent_channel(irc, 'relay', channel) except KeyError: log.warning('(%s) relay: failed to remove persistent channel %r on delink', irc.name, channel, exc_info=True) From c7da7f0025b0482a02be9b35a08616ae2c171662 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 13:19:43 -0700 Subject: [PATCH 07/11] ServiceBot: allow sending service parts with reasons --- utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils.py b/utils.py index a108973..82f3c52 100644 --- a/utils.py +++ b/utils.py @@ -230,7 +230,7 @@ class ServiceBot(): else: log.warning('(%s/%s) Ignoring invalid channel %r', irc.name, self.name, channel) - def part(self, irc, channels): + def part(self, irc, channels, reason=''): """ Parts the given service bot from the given channel(s) if no plugins still register it as a persistent dynamic channel. @@ -268,12 +268,12 @@ class ServiceBot(): persistent_channels) continue to_part.append(channel) - irc.part(uid, channel, '') # TODO: configurable part message? + irc.part(uid, channel, reason) else: log.debug('(%s/%s) Ignoring part to %r, we are not there', irc.name, self.name, channel) continue - irc.call_hooks([uid, 'PYLINK_SERVICE_PART', {'channels': to_part, 'text': ''}]) + irc.call_hooks([uid, 'PYLINK_SERVICE_PART', {'channels': to_part, 'text': reason}]) def reply(self, irc, text, notice=None, private=None): """Replies to a message as the service in question.""" @@ -439,14 +439,14 @@ class ServiceBot(): if try_join: self.join(irc, [channel]) - def remove_persistent_channel(self, irc, namespace, channel, try_part=True): + def remove_persistent_channel(self, irc, namespace, channel, try_part=True, part_reason=''): """ Removes a persistent channel from the service bot on the given network and namespace. """ chanlist = self.dynamic_channels[namespace][irc.name].remove(channel) if try_part and irc.connected.is_set(): - self.part(irc, [channel]) + self.part(irc, [channel], reason=part_reason) def get_persistent_channels(self, irc, namespace=None): """ From 72c2fa38e9007e50ef3ef5fb993f5e0b3908572b Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 13:19:57 -0700 Subject: [PATCH 08/11] relay: consistently use "Channel delinked." as part message for service bots too --- plugins/relay.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index cc225b7..b27d7e2 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -12,6 +12,8 @@ from pylinkirc.coremods import permissions # Sets the timeout to wait for as individual servers / the PyLink daemon to start up. TCONDITION_TIMEOUT = 2 +CHANNEL_DELINKED_PARTMSG = 'Channel delinked.' + ### GLOBAL (statekeeping) VARIABLES relayusers = defaultdict(dict) relayservers = defaultdict(dict) @@ -540,7 +542,7 @@ def remove_channel(irc, channel): return try: - world.services['pylink'].remove_persistent_channel(irc, 'relay', channel) + world.services['pylink'].remove_persistent_channel(irc, 'relay', channel, part_reason=CHANNEL_DELINKED_PARTMSG) except KeyError: log.warning('(%s) relay: failed to remove persistent channel %r on delink', irc.name, channel, exc_info=True) @@ -558,11 +560,11 @@ def remove_channel(irc, channel): sbot = irc.get_service_bot(user) if sbot: try: - sbot.remove_persistent_channel(irc, 'relay', channel) + sbot.remove_persistent_channel(irc, 'relay', channel, part_reason=CHANNEL_DELINKED_PARTMSG) except KeyError: pass else: - irc.part(user, channel, 'Channel delinked.') + irc.part(user, channel, CHANNEL_DELINKED_PARTMSG) if user != irc.pseudoclient.uid and not irc.users[user].channels: remoteuser = get_orig_user(irc, user) del relayusers[remoteuser][irc.name] @@ -832,7 +834,7 @@ def relay_part(irc, *args, **kwargs): pass # Part the relay client with the channel delinked message. - remoteirc.part(remoteuser, remotechan, 'Channel delinked.') + remoteirc.part(remoteuser, remotechan, CHANNEL_DELINKED_PARTMSG) # If the relay client no longer has any channels, quit them to prevent inflating /lusers. if is_relay_client(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels: From bf4863eb6d2dae12c3cc11c35fea4f2d5b0865c8 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 13:20:45 -0700 Subject: [PATCH 09/11] relay, ServiceBot: remove dead code --- plugins/relay.py | 10 ---------- utils.py | 13 ------------- 2 files changed, 23 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index b27d7e2..fd3fad6 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -511,16 +511,6 @@ def initialize_channel(irc, channel): # Join their (remote) users and set their modes, if applicable. if remotechan in remoteirc.channels: rc = remoteirc.channels[remotechan] - ''' - if not hasattr(rc, '_relay_initial_burst'): - rc._relay_initial_burst = threading.Event() - - if rc._relay_initial_burst.is_set(): - log.debug('(%s) relay.initialize_channel: skipping inbound burst from %s/%s => %s/%s ' - 'as it has already been bursted', irc.name, remoteirc.name, remotechan, irc.name, channel) - continue - rc._relay_initial_burst.set() - ''' relay_joins(remoteirc, remotechan, rc.users, rc.ts, targetirc=irc) # Only update the topic if it's different from what we already have, diff --git a/utils.py b/utils.py index 82f3c52..434b2e2 100644 --- a/utils.py +++ b/utils.py @@ -248,19 +248,6 @@ class ServiceBot(): to_part = [] persistent_channels = self.get_persistent_channels(irc) for channel in channels: - ''' - if channel in irc.channels and uid in irc.channels[channel].users: - for dch_namespace, dch_data in self.dynamic_channels.items(): - if irc.name in dch_data and channel in dch_data[irc.name]: - log.debug('(%s/%s) Not parting %r because namespace %r still registers it ' - 'as a dynamic channel.', irc.name, self.name, channel, - dch_namespace) - break - else: - to_part.append(channel) - irc.part(uid, channel, '') # TODO: configurable part message? - ''' - if channel in irc.channels and uid in irc.channels[channel].users: if channel in persistent_channels: log.debug('(%s/%s) Not parting %r because it is registered ' From 8aa67b93fa99d4b0996c72982adbeed29322a500 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 21:10:08 -0700 Subject: [PATCH 10/11] automode: also rejoin DB channels on reload --- plugins/automode.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/plugins/automode.py b/plugins/automode.py index d1d329c..1a69c44 100644 --- a/plugins/automode.py +++ b/plugins/automode.py @@ -26,6 +26,20 @@ db = datastore.store default_permissions = {"$ircop": ['automode.manage.relay_owned', 'automode.sync.relay_owned', 'automode.list']} +def _join_db_channels(irc): + """ + Joins the Automode service client to channels on the current network in its DB. + """ + if not irc.connected.is_set(): + log.debug('(%s) _join_db_channels: aborting, network not ready yet', irc.name) + return + + for entry in db: + netname, channel = entry.split('#', 1) + channel = '#' + channel + if netname == irc.name: + modebot.add_persistent_channel(irc, 'automode', channel) + def main(irc=None): """Main function, called during plugin loading at start.""" @@ -35,6 +49,10 @@ def main(irc=None): # Register our permissions. permissions.add_default_permissions(default_permissions) + if irc: # After initial startup + for ircobj in world.networkobjects.values(): + _join_db_channels(ircobj) + def die(irc=None): """Saves the Automode database and quit.""" datastore.die() @@ -115,15 +133,8 @@ def match(irc, channel, uids=None): def handle_endburst(irc, source, command, args): """ENDBURST hook handler - used to join the Automode service to channels where it has entries.""" - if source != irc.uplink: - return - - for entry in db: - netname, channel = entry.split('#', 1) - channel = '#' + channel - if netname == irc.name: - modebot.add_persistent_channel(irc, 'automode', channel) - + if source == irc.uplink: + _join_db_channels(irc) utils.add_hook(handle_endburst, 'ENDBURST') def handle_join(irc, source, command, args): From 5a0cb9a4ff7e0cf6357e2a872de3e667e97eec03 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 5 May 2018 21:51:15 -0700 Subject: [PATCH 11/11] automode: add/remove persistent channels on set/del/clearacc --- plugins/automode.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/automode.py b/plugins/automode.py index 1a69c44..6472da3 100644 --- a/plugins/automode.py +++ b/plugins/automode.py @@ -230,8 +230,8 @@ def setacc(irc, source, args): log.info('(%s) %s set modes +%s for %s on %s', ircobj.name, irc.get_hostmask(source), modes, mask, channel) reply(irc, "Done. \x02%s\x02 now has modes \x02+%s\x02 in \x02%s\x02." % (mask, modes, channel)) - # Join the Automode bot to the channel if not explicitly told to. - modebot.join(ircobj, channel) + # Join the Automode bot to the channel persistently. + modebot.add_persistent_channel(irc, 'automode', channel) modebot.add_cmd(setacc, aliases=('setaccess', 'set'), featured=True) @@ -265,6 +265,7 @@ def delacc(irc, source, args): if not dbentry: log.debug("Automode: purging empty channel pair %s/%s", ircobj.name, channel) del db[ircobj.name+channel] + modebot.remove_persistent_channel(irc, 'automode', channel) modebot.add_cmd(delacc, aliases=('delaccess', 'del'), featured=True) @@ -344,6 +345,7 @@ def clearacc(irc, source, args): del db[ircobj.name+channel] log.info('(%s) %s cleared modes on %s', ircobj.name, irc.get_hostmask(source), channel) reply(irc, "Done. Removed all Automode access entries for \x02%s\x02." % channel) + modebot.remove_persistent_channel(irc, 'automode', channel) else: error(irc, "No Automode access entries exist for \x02%s\x02." % channel)