2016-06-21 20:25:47 +02:00
|
|
|
"""
|
|
|
|
service_support.py - Implements handlers for the PyLink ServiceBot API.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from pylinkirc import utils, world, conf
|
|
|
|
from pylinkirc.log import log
|
|
|
|
|
|
|
|
def spawn_service(irc, source, command, args):
|
|
|
|
"""Handles new service bot introductions."""
|
|
|
|
|
|
|
|
if not irc.connected.is_set():
|
|
|
|
return
|
|
|
|
|
|
|
|
# Service name
|
|
|
|
name = args['name']
|
|
|
|
|
2017-06-30 08:01:39 +02:00
|
|
|
if name != 'pylink' and not irc.has_cap('can-spawn-clients'):
|
2017-03-24 07:53:43 +01:00
|
|
|
log.debug("(%s) Not spawning service %s because the server doesn't support spawning clients",
|
|
|
|
irc.name, name)
|
|
|
|
return
|
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
# Get the ServiceBot object.
|
|
|
|
sbot = world.services[name]
|
|
|
|
|
2017-08-31 04:29:26 +02:00
|
|
|
old_userobj = irc.users.get(sbot.uids.get(irc.name))
|
|
|
|
if old_userobj and old_userobj.service:
|
2017-08-31 04:50:25 +02:00
|
|
|
# A client already exists, so don't respawn it.
|
|
|
|
log.debug('(%s) spawn_service: Not respawning service %r as service client %r already exists.', irc.name, name,
|
|
|
|
irc.pseudoclient.nick)
|
|
|
|
return
|
|
|
|
|
|
|
|
if name == 'pylink' and irc.pseudoclient:
|
|
|
|
# irc.pseudoclient already exists, reuse values from it but
|
|
|
|
# spawn a new client. This is used for protocols like Clientbot,
|
|
|
|
# so that they can override the main service nick, among other things.
|
|
|
|
log.debug('(%s) spawn_service: Using existing nick %r for service %r', irc.name, irc.pseudoclient.nick, name)
|
|
|
|
userobj = irc.pseudoclient
|
2017-08-22 08:05:27 +02:00
|
|
|
userobj.opertype = "PyLink Service"
|
|
|
|
userobj.manipulatable = sbot.manipulatable
|
2017-05-13 03:27:54 +02:00
|
|
|
else:
|
2017-08-22 08:05:27 +02:00
|
|
|
# No client exists, spawn a new one
|
|
|
|
nick = sbot.get_nick(irc)
|
|
|
|
ident = sbot.get_ident(irc)
|
|
|
|
host = sbot.get_host(irc)
|
|
|
|
realname = sbot.get_realname(irc)
|
|
|
|
|
|
|
|
# Spawning service clients with these umodes where supported. servprotect usage is a
|
|
|
|
# configuration option.
|
|
|
|
preferred_modes = ['oper', 'hideoper', 'hidechans', 'invisible', 'bot']
|
|
|
|
modes = []
|
|
|
|
|
|
|
|
if conf.conf['pylink'].get('protect_services'):
|
|
|
|
preferred_modes.append('servprotect')
|
|
|
|
|
|
|
|
for mode in preferred_modes:
|
|
|
|
mode = irc.umodes.get(mode)
|
|
|
|
if mode:
|
|
|
|
modes.append((mode, None))
|
|
|
|
|
|
|
|
# Track the service's UIDs on each network.
|
|
|
|
log.debug('(%s) spawn_service: Spawning new client %s for service %s', irc.name, nick, name)
|
2017-07-01 06:30:20 +02:00
|
|
|
userobj = irc.spawn_client(nick, ident, host, modes=modes, opertype="PyLink Service",
|
2017-08-22 08:05:27 +02:00
|
|
|
realname=realname, manipulatable=sbot.manipulatable)
|
2016-06-21 20:25:47 +02:00
|
|
|
|
2017-07-01 06:44:31 +02:00
|
|
|
# Store the service name in the User object for easier access.
|
2016-11-10 04:07:01 +01:00
|
|
|
userobj.service = name
|
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
sbot.uids[irc.name] = u = userobj.uid
|
|
|
|
|
|
|
|
# Special case: if this is the main PyLink client being spawned,
|
|
|
|
# assign this as irc.pseudoclient.
|
2017-08-22 08:05:27 +02:00
|
|
|
if name == 'pylink' and not irc.pseudoclient:
|
2017-05-13 03:27:54 +02:00
|
|
|
log.debug('(%s) spawn_service: irc.pseudoclient set to UID %s', irc.name, u)
|
2016-06-21 20:25:47 +02:00
|
|
|
irc.pseudoclient = userobj
|
|
|
|
|
2018-04-12 21:45:33 +02:00
|
|
|
# Enumerate & join network defined channels.
|
|
|
|
sbot.join(irc, sbot.get_persistent_channels(irc))
|
2016-06-21 20:25:47 +02:00
|
|
|
|
|
|
|
utils.add_hook(spawn_service, 'PYLINK_NEW_SERVICE')
|
|
|
|
|
|
|
|
def handle_disconnect(irc, source, command, args):
|
|
|
|
"""Handles network disconnections."""
|
|
|
|
for name, sbot in world.services.items():
|
|
|
|
try:
|
|
|
|
del sbot.uids[irc.name]
|
2016-07-02 06:08:50 +02:00
|
|
|
log.debug("coremods.service_support: removing uids[%s] from service bot %s", irc.name, sbot.name)
|
2016-06-21 20:25:47 +02:00
|
|
|
except KeyError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
utils.add_hook(handle_disconnect, 'PYLINK_DISCONNECT')
|
|
|
|
|
|
|
|
def handle_endburst(irc, source, command, args):
|
|
|
|
"""Handles network bursts."""
|
|
|
|
if source == irc.uplink:
|
|
|
|
log.debug('(%s): spawning service bots now.', irc.name)
|
|
|
|
|
|
|
|
# We just connected. Burst all our registered services.
|
|
|
|
for name, sbot in world.services.items():
|
|
|
|
spawn_service(irc, source, command, {'name': name})
|
|
|
|
|
2018-05-05 22:05:43 +02:00
|
|
|
utils.add_hook(handle_endburst, 'ENDBURST', priority=500)
|
2016-06-21 20:25:47 +02:00
|
|
|
|
2016-06-25 22:58:59 +02:00
|
|
|
def handle_kill(irc, source, command, args):
|
|
|
|
"""Handle KILLs to PyLink service bots, respawning them as needed."""
|
|
|
|
target = args['target']
|
2017-11-12 19:54:30 +01:00
|
|
|
if irc.pseudoclient and target == irc.pseudoclient.uid:
|
|
|
|
irc.pseudoclient = None
|
2017-05-13 03:45:27 +02:00
|
|
|
userdata = args.get('userdata')
|
2017-06-30 08:01:39 +02:00
|
|
|
sbot = irc.get_service_bot(target)
|
2018-04-14 05:34:26 +02:00
|
|
|
servicename = None
|
2018-05-05 21:58:34 +02:00
|
|
|
|
|
|
|
if userdata and hasattr(userdata, 'service'): # Look for the target's service name attribute
|
|
|
|
servicename = userdata.service
|
|
|
|
elif sbot: # Or their service bot instance
|
2017-05-13 03:45:27 +02:00
|
|
|
servicename = sbot.name
|
|
|
|
if servicename:
|
2017-08-02 16:14:57 +02:00
|
|
|
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'))
|
2017-05-13 03:45:27 +02:00
|
|
|
spawn_service(irc, source, command, {'name': servicename})
|
|
|
|
|
2016-06-25 22:58:59 +02:00
|
|
|
utils.add_hook(handle_kill, 'KILL')
|
|
|
|
|
2018-04-12 21:45:33 +02:00
|
|
|
def handle_join(irc, source, command, args):
|
|
|
|
"""Monitors channel joins for dynamic service bot joining."""
|
2018-04-18 00:21:40 +02:00
|
|
|
if irc.has_cap('visible-state-only'):
|
|
|
|
# No-op on bot-only servers.
|
|
|
|
return
|
|
|
|
|
2018-04-12 21:45:33 +02:00
|
|
|
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."""
|
2018-04-18 00:21:40 +02:00
|
|
|
if irc.has_cap('visible-state-only'):
|
|
|
|
# No-op on bot-only servers.
|
|
|
|
return
|
|
|
|
|
2018-04-12 21:45:33 +02:00
|
|
|
# If all remaining users in the channel are service bots, make them all part.
|
2018-05-09 10:37:59 +02:00
|
|
|
if all(irc.get_service_bot(u) for u in irc.channels[channel].users):
|
2018-04-12 21:45:33 +02:00
|
|
|
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')
|
|
|
|
|
2016-06-25 22:58:59 +02:00
|
|
|
def handle_kick(irc, source, command, args):
|
|
|
|
"""Handle KICKs to the PyLink service bots, rejoining channels as needed."""
|
|
|
|
channel = args['channel']
|
2018-04-12 21:45:33 +02:00
|
|
|
# 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)
|
2018-05-05 21:58:34 +02:00
|
|
|
if sbot and channel in sbot.get_persistent_channels(irc):
|
2018-04-12 21:45:33 +02:00
|
|
|
sbot.join(irc, channel)
|
2016-06-25 22:58:59 +02:00
|
|
|
utils.add_hook(handle_kick, 'KICK')
|
|
|
|
|
|
|
|
def handle_commands(irc, source, command, args):
|
|
|
|
"""Handle commands sent to the PyLink service bots (PRIVMSG)."""
|
|
|
|
target = args['target']
|
|
|
|
text = args['text']
|
|
|
|
|
2017-06-30 08:01:39 +02:00
|
|
|
sbot = irc.get_service_bot(target)
|
2016-06-25 22:58:59 +02:00
|
|
|
if sbot:
|
|
|
|
sbot.call_cmd(irc, source, text)
|
|
|
|
|
|
|
|
utils.add_hook(handle_commands, 'PRIVMSG')
|
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
# Register the main PyLink service. All command definitions MUST go after this!
|
2018-04-12 21:45:33 +02:00
|
|
|
# TODO: be more specific in description, and possibly allow plugins to modify this to mention
|
2016-07-01 02:44:35 +02:00
|
|
|
# their features?
|
2017-03-11 08:27:10 +01:00
|
|
|
mydesc = "\x02PyLink\x02 provides extended network services for IRC."
|
2018-03-03 04:58:10 +01:00
|
|
|
utils.register_service('pylink', default_nick="PyLink", desc=mydesc, manipulatable=True)
|