mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-17 23:52:49 +01:00
Merge branch 'wip/dynamic-services' into devel
This commit is contained in:
commit
4d9fbc55ba
@ -73,9 +73,8 @@ def spawn_service(irc, source, command, args):
|
||||
log.debug('(%s) spawn_service: irc.pseudoclient set to UID %s', irc.name, u)
|
||||
irc.pseudoclient = userobj
|
||||
|
||||
channels = set(irc.serverdata.get(name+'_channels', [])) | set(irc.serverdata.get('channels', [])) | \
|
||||
sbot.extra_channels.get(irc.name, set())
|
||||
sbot.join(irc, channels)
|
||||
# Enumerate & join network defined channels.
|
||||
sbot.join(irc, sbot.get_persistent_channels(irc))
|
||||
|
||||
utils.add_hook(spawn_service, 'PYLINK_NEW_SERVICE')
|
||||
|
||||
@ -108,26 +107,66 @@ def handle_kill(irc, source, command, args):
|
||||
irc.pseudoclient = None
|
||||
userdata = args.get('userdata')
|
||||
sbot = irc.get_service_bot(target)
|
||||
servicename = None
|
||||
|
||||
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 = 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
|
||||
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):
|
||||
"""Monitors channel joins for dynamic service bot joining."""
|
||||
channel = args['channel']
|
||||
users = irc.channels[channel].users
|
||||
for servicename, sbot in world.services.items():
|
||||
if channel in sbot.get_persistent_channels(irc) and \
|
||||
sbot.uids.get(irc.name) not in users:
|
||||
log.debug('(%s) Dynamically joining service %r to channel %r.', irc.name, servicename, channel)
|
||||
sbot.join(irc, channel)
|
||||
utils.add_hook(handle_join, 'JOIN')
|
||||
utils.add_hook(handle_join, 'PYLINK_SERVICE_JOIN')
|
||||
|
||||
def _services_dynamic_part(irc, channel):
|
||||
"""Dynamically removes service bots from empty channels."""
|
||||
# If all remaining users in the channel are service bots, make them all part.
|
||||
if all(irc.is_internal_client(u) for u in irc.channels[channel].users):
|
||||
for u in irc.channels[channel].users.copy():
|
||||
sbot = irc.get_service_bot(u)
|
||||
if sbot:
|
||||
log.debug('(%s) Dynamically parting service %r from channel %r.', irc.name, sbot.name, channel)
|
||||
irc.part(u, channel)
|
||||
return True
|
||||
|
||||
def handle_part(irc, source, command, args):
|
||||
"""Monitors channel joins for dynamic service bot joining."""
|
||||
for channel in args['channels']:
|
||||
_services_dynamic_part(irc, channel)
|
||||
utils.add_hook(handle_part, 'PART')
|
||||
|
||||
def handle_kick(irc, source, command, args):
|
||||
"""Handle KICKs to the PyLink service bots, rejoining channels as needed."""
|
||||
kicked = args['target']
|
||||
channel = args['channel']
|
||||
sbot = irc.get_service_bot(kicked)
|
||||
if sbot:
|
||||
sbot.join(irc, channel)
|
||||
# Skip autorejoin routines if the channel is now empty.
|
||||
if not _services_dynamic_part(irc, channel):
|
||||
kicked = args['target']
|
||||
sbot = irc.get_service_bot(kicked)
|
||||
if sbot:
|
||||
sbot.join(irc, channel)
|
||||
utils.add_hook(handle_kick, 'KICK')
|
||||
|
||||
def handle_commands(irc, source, command, args):
|
||||
@ -142,7 +181,7 @@ def handle_commands(irc, source, command, args):
|
||||
utils.add_hook(handle_commands, 'PRIVMSG')
|
||||
|
||||
# Register the main PyLink service. All command definitions MUST go after this!
|
||||
# TODO: be more specific, and possibly allow plugins to modify this to mention
|
||||
# TODO: be more specific in description, and possibly allow plugins to modify this to mention
|
||||
# their features?
|
||||
mydesc = "\x02PyLink\x02 provides extended network services for IRC."
|
||||
utils.register_service('pylink', default_nick="PyLink", desc=mydesc, manipulatable=True)
|
||||
|
@ -539,23 +539,19 @@ def remove_channel(irc, channel):
|
||||
if irc is None:
|
||||
return
|
||||
|
||||
if channel not in map(str.lower, irc.serverdata.get('channels', [])):
|
||||
world.services['pylink'].extra_channels[irc.name].discard(channel)
|
||||
if irc.pseudoclient:
|
||||
irc.part(irc.pseudoclient.uid, channel, 'Channel delinked.')
|
||||
world.services['pylink'].dynamic_channels[irc.name].discard(channel)
|
||||
|
||||
relay = get_relay(irc, channel)
|
||||
if relay and channel in irc.channels:
|
||||
for user in irc.channels[channel].users.copy():
|
||||
if not is_relay_client(irc, user):
|
||||
# Relay a /part of all local users.
|
||||
if not irc.is_internal_client(user):
|
||||
relay_part(irc, channel, user)
|
||||
# Don't ever part the main client from any of its autojoin channels.
|
||||
else:
|
||||
if user == irc.pseudoclient.uid and channel in \
|
||||
irc.serverdata.get('channels', []):
|
||||
# Part and quit all relay clients.
|
||||
if irc.get_service_bot(user): # ...but ignore service bots
|
||||
continue
|
||||
irc.part(user, channel, 'Channel delinked.')
|
||||
# Don't ever quit it either...
|
||||
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
||||
remoteuser = get_orig_user(irc, user)
|
||||
del relayusers[remoteuser][irc.name]
|
||||
|
45
utils.py
45
utils.py
@ -13,7 +13,7 @@ import collections
|
||||
import argparse
|
||||
|
||||
from .log import log
|
||||
from . import world, conf
|
||||
from . import world, conf, structures
|
||||
|
||||
# Load the protocol and plugin packages.
|
||||
from pylinkirc import protocols, plugins
|
||||
@ -152,9 +152,10 @@ class ServiceBot():
|
||||
# spawned.
|
||||
self.uids = {}
|
||||
|
||||
# Track what channels other than those defined in the config
|
||||
# that the bot should join by default.
|
||||
self.extra_channels = collections.defaultdict(set)
|
||||
# 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]))
|
||||
|
||||
# Service description, used in the default help command if one is given.
|
||||
self.desc = desc
|
||||
@ -183,9 +184,14 @@ class ServiceBot():
|
||||
else:
|
||||
raise NotImplementedError("Network specific plugins not supported yet.")
|
||||
|
||||
def join(self, irc, channels, autojoin=True):
|
||||
def join(self, irc, channels, ignore_empty=True):
|
||||
"""
|
||||
Joins the given service bot to the given channel(s).
|
||||
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.
|
||||
"""
|
||||
|
||||
if isinstance(irc, str):
|
||||
@ -198,10 +204,6 @@ class ServiceBot():
|
||||
channels = [channels]
|
||||
channels = set(channels)
|
||||
|
||||
if autojoin:
|
||||
log.debug('(%s/%s) Adding channels %s to autojoin', netname, self.name, channels)
|
||||
self.extra_channels[netname] |= channels
|
||||
|
||||
# If the network was given as a string, look up the Irc object here.
|
||||
try:
|
||||
irc = world.networkobjects[netname]
|
||||
@ -209,6 +211,10 @@ class ServiceBot():
|
||||
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:
|
||||
@ -221,10 +227,16 @@ class ServiceBot():
|
||||
|
||||
for chan in channels:
|
||||
if irc.is_channel(chan):
|
||||
if chan in irc.channels and u in irc.channels[chan].users:
|
||||
log.debug('(%s) Skipping join of services %s to channel %s - it is already present', irc.name, self.name, 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)
|
||||
continue
|
||||
elif ignore_empty:
|
||||
log.debug('(%s) Skipping joining service %r to empty channel %r', irc.name, self.name, chan)
|
||||
continue
|
||||
|
||||
log.debug('(%s) Joining services %s to channel %s with modes %r', irc.name, self.name, chan, joinmodes)
|
||||
|
||||
if joinmodes: # Modes on join were specified; use SJOIN to burst our service
|
||||
irc.proto.sjoin(irc.sid, chan, [(joinmodes, u)])
|
||||
else:
|
||||
@ -387,6 +399,15 @@ 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):
|
||||
"""
|
||||
Returns a set of persistent channels for the IRC network.
|
||||
"""
|
||||
channels = self.dynamic_channels[irc.name].copy()
|
||||
channels |= set(irc.serverdata.get(self.name+'_channels', []))
|
||||
channels |= set(irc.serverdata.get('channels', []))
|
||||
return channels
|
||||
|
||||
def _show_command_help(self, irc, command, private=False, shortform=False):
|
||||
"""
|
||||
Shows help for the given command.
|
||||
|
Loading…
Reference in New Issue
Block a user