mirror of
				https://github.com/jlu5/PyLink.git
				synced 2025-11-04 08:57:25 +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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user