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)
|
log.debug('(%s) spawn_service: irc.pseudoclient set to UID %s', irc.name, u)
|
||||||
irc.pseudoclient = userobj
|
irc.pseudoclient = userobj
|
||||||
|
|
||||||
channels = set(irc.serverdata.get(name+'_channels', [])) | set(irc.serverdata.get('channels', [])) | \
|
# Enumerate & join network defined channels.
|
||||||
sbot.extra_channels.get(irc.name, set())
|
sbot.join(irc, sbot.get_persistent_channels(irc))
|
||||||
sbot.join(irc, channels)
|
|
||||||
|
|
||||||
utils.add_hook(spawn_service, 'PYLINK_NEW_SERVICE')
|
utils.add_hook(spawn_service, 'PYLINK_NEW_SERVICE')
|
||||||
|
|
||||||
@ -108,26 +107,66 @@ def handle_kill(irc, source, command, args):
|
|||||||
irc.pseudoclient = None
|
irc.pseudoclient = None
|
||||||
userdata = args.get('userdata')
|
userdata = args.get('userdata')
|
||||||
sbot = irc.get_service_bot(target)
|
sbot = irc.get_service_bot(target)
|
||||||
servicename = None
|
|
||||||
|
|
||||||
if userdata and hasattr(userdata, 'service'): # Look for the target's service name attribute
|
servicename = None
|
||||||
servicename = userdata.service
|
channels = []
|
||||||
elif sbot: # Or their service bot instance
|
|
||||||
|
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
|
servicename = sbot.name
|
||||||
|
channels = irc.users[target].channels
|
||||||
if servicename:
|
if servicename:
|
||||||
log.info('(%s) Received kill to service %r (nick: %r) from %s (reason: %r).', irc.name, 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'))
|
userdata.nick if userdata else irc.users[target].nick, irc.get_hostmask(source), args.get('text'))
|
||||||
spawn_service(irc, source, command, {'name': servicename})
|
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')
|
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):
|
def handle_kick(irc, source, command, args):
|
||||||
"""Handle KICKs to the PyLink service bots, rejoining channels as needed."""
|
"""Handle KICKs to the PyLink service bots, rejoining channels as needed."""
|
||||||
kicked = args['target']
|
|
||||||
channel = args['channel']
|
channel = args['channel']
|
||||||
sbot = irc.get_service_bot(kicked)
|
# Skip autorejoin routines if the channel is now empty.
|
||||||
if sbot:
|
if not _services_dynamic_part(irc, channel):
|
||||||
sbot.join(irc, channel)
|
kicked = args['target']
|
||||||
|
sbot = irc.get_service_bot(kicked)
|
||||||
|
if sbot:
|
||||||
|
sbot.join(irc, channel)
|
||||||
utils.add_hook(handle_kick, 'KICK')
|
utils.add_hook(handle_kick, 'KICK')
|
||||||
|
|
||||||
def handle_commands(irc, source, command, args):
|
def handle_commands(irc, source, command, args):
|
||||||
@ -142,7 +181,7 @@ def handle_commands(irc, source, command, args):
|
|||||||
utils.add_hook(handle_commands, 'PRIVMSG')
|
utils.add_hook(handle_commands, 'PRIVMSG')
|
||||||
|
|
||||||
# Register the main PyLink service. All command definitions MUST go after this!
|
# 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?
|
# their features?
|
||||||
mydesc = "\x02PyLink\x02 provides extended network services for IRC."
|
mydesc = "\x02PyLink\x02 provides extended network services for IRC."
|
||||||
utils.register_service('pylink', default_nick="PyLink", desc=mydesc, manipulatable=True)
|
utils.register_service('pylink', default_nick="PyLink", desc=mydesc, manipulatable=True)
|
||||||
|
@ -539,23 +539,19 @@ def remove_channel(irc, channel):
|
|||||||
if irc is None:
|
if irc is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if channel not in map(str.lower, irc.serverdata.get('channels', [])):
|
world.services['pylink'].dynamic_channels[irc.name].discard(channel)
|
||||||
world.services['pylink'].extra_channels[irc.name].discard(channel)
|
|
||||||
if irc.pseudoclient:
|
|
||||||
irc.part(irc.pseudoclient.uid, channel, 'Channel delinked.')
|
|
||||||
|
|
||||||
relay = get_relay(irc, channel)
|
relay = get_relay(irc, channel)
|
||||||
if relay and channel in irc.channels:
|
if relay and channel in irc.channels:
|
||||||
for user in irc.channels[channel].users.copy():
|
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)
|
relay_part(irc, channel, user)
|
||||||
# Don't ever part the main client from any of its autojoin channels.
|
|
||||||
else:
|
else:
|
||||||
if user == irc.pseudoclient.uid and channel in \
|
# Part and quit all relay clients.
|
||||||
irc.serverdata.get('channels', []):
|
if irc.get_service_bot(user): # ...but ignore service bots
|
||||||
continue
|
continue
|
||||||
irc.part(user, channel, 'Channel delinked.')
|
irc.part(user, channel, 'Channel delinked.')
|
||||||
# Don't ever quit it either...
|
|
||||||
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
||||||
remoteuser = get_orig_user(irc, user)
|
remoteuser = get_orig_user(irc, user)
|
||||||
del relayusers[remoteuser][irc.name]
|
del relayusers[remoteuser][irc.name]
|
||||||
|
45
utils.py
45
utils.py
@ -13,7 +13,7 @@ import collections
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from .log import log
|
from .log import log
|
||||||
from . import world, conf
|
from . import world, conf, structures
|
||||||
|
|
||||||
# Load the protocol and plugin packages.
|
# Load the protocol and plugin packages.
|
||||||
from pylinkirc import protocols, plugins
|
from pylinkirc import protocols, plugins
|
||||||
@ -152,9 +152,10 @@ class ServiceBot():
|
|||||||
# spawned.
|
# spawned.
|
||||||
self.uids = {}
|
self.uids = {}
|
||||||
|
|
||||||
# Track what channels other than those defined in the config
|
# Track plugin-defined persistent channels. The bot will leave them if they're empty,
|
||||||
# that the bot should join by default.
|
# and rejoin whenever someone else does.
|
||||||
self.extra_channels = collections.defaultdict(set)
|
self.dynamic_channels = structures.KeyedDefaultdict(lambda netname:
|
||||||
|
structures.IRCCaseInsensitiveSet(world.networkobjects[netname]))
|
||||||
|
|
||||||
# Service description, used in the default help command if one is given.
|
# Service description, used in the default help command if one is given.
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
@ -183,9 +184,14 @@ class ServiceBot():
|
|||||||
else:
|
else:
|
||||||
raise NotImplementedError("Network specific plugins not supported yet.")
|
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):
|
if isinstance(irc, str):
|
||||||
@ -198,10 +204,6 @@ class ServiceBot():
|
|||||||
channels = [channels]
|
channels = [channels]
|
||||||
channels = set(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.
|
# If the network was given as a string, look up the Irc object here.
|
||||||
try:
|
try:
|
||||||
irc = world.networkobjects[netname]
|
irc = world.networkobjects[netname]
|
||||||
@ -209,6 +211,10 @@ class ServiceBot():
|
|||||||
log.debug('(%s/%s) Skipping join(), IRC object not initialized yet', netname, self.name)
|
log.debug('(%s/%s) Skipping join(), IRC object not initialized yet', netname, self.name)
|
||||||
return
|
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:
|
try:
|
||||||
u = self.uids[irc.name]
|
u = self.uids[irc.name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -221,10 +227,16 @@ class ServiceBot():
|
|||||||
|
|
||||||
for chan in channels:
|
for chan in channels:
|
||||||
if irc.is_channel(chan):
|
if irc.is_channel(chan):
|
||||||
if chan in irc.channels and u in irc.channels[chan].users:
|
if chan in irc.channels:
|
||||||
log.debug('(%s) Skipping join of services %s to channel %s - it is already present', irc.name, self.name, chan)
|
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
|
continue
|
||||||
|
|
||||||
log.debug('(%s) Joining services %s to channel %s with modes %r', irc.name, self.name, chan, joinmodes)
|
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
|
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, chan, [(joinmodes, u)])
|
||||||
else:
|
else:
|
||||||
@ -387,6 +399,15 @@ class ServiceBot():
|
|||||||
sbconf = conf.conf.get(self.name, {})
|
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
|
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):
|
def _show_command_help(self, irc, command, private=False, shortform=False):
|
||||||
"""
|
"""
|
||||||
Shows help for the given command.
|
Shows help for the given command.
|
||||||
|
Loading…
Reference in New Issue
Block a user