2016-07-23 09:55:06 +02:00
|
|
|
# relay_clientbot.py: Clientbot extensions for Relay
|
2016-07-23 21:05:22 +02:00
|
|
|
import string
|
2016-08-09 05:59:15 +02:00
|
|
|
import collections
|
2016-08-09 07:20:31 +02:00
|
|
|
import time
|
2016-07-23 09:55:06 +02:00
|
|
|
|
2016-07-23 21:05:22 +02:00
|
|
|
from pylinkirc import utils, conf, world
|
2016-07-23 09:55:06 +02:00
|
|
|
from pylinkirc.log import log
|
|
|
|
|
2016-08-04 21:50:38 +02:00
|
|
|
# TODO: document configurable styles in relay::clientbot_styles::COMMAND_NAME
|
|
|
|
# These use template strings as documented @ https://docs.python.org/3/library/string.html#template-strings
|
|
|
|
default_styles = {'MESSAGE': '\x02[$colored_netname]\x02 <$colored_sender> $text',
|
|
|
|
'KICK': '\x02[$colored_netname]\x02 - $colored_sender$sender_identhost has kicked $target_nick from $channel ($text)',
|
|
|
|
'PART': '\x02[$colored_netname]\x02 - $colored_sender$sender_identhost has left $channel ($text)',
|
|
|
|
'JOIN': '\x02[$colored_netname]\x02 - $colored_sender$sender_identhost has joined $channel',
|
|
|
|
'NICK': '\x02[$colored_netname]\x02 - $colored_sender$sender_identhost is now known as $newnick',
|
|
|
|
'QUIT': '\x02[$colored_netname]\x02 - $colored_sender$sender_identhost has quit ($text)',
|
|
|
|
'ACTION': '\x02[$colored_netname]\x02 * $colored_sender $text',
|
|
|
|
'NOTICE': '\x02[$colored_netname]\x02 - Notice from $colored_sender: $text',
|
|
|
|
'SQUIT': '\x02[$colored_netname]\x02 - Netsplit lost users: $colored_nicks',
|
|
|
|
'SJOIN': '\x02[$colored_netname]\x02 - Netjoin gained users: $colored_nicks',
|
2016-07-23 21:25:52 +02:00
|
|
|
}
|
2016-07-23 09:55:06 +02:00
|
|
|
|
2016-07-23 21:05:22 +02:00
|
|
|
def color_text(s):
|
|
|
|
"""
|
|
|
|
Returns a colorized version of the given text based on a simple hash algorithm
|
|
|
|
(sum of all characters).
|
|
|
|
"""
|
|
|
|
colors = ('02', '03', '04', '05', '06', '07', '08', '09', '10', '11',
|
|
|
|
'12', '13')
|
|
|
|
num = sum([ord(char) for char in s])
|
|
|
|
num = num % len(colors)
|
|
|
|
return "\x03%s%s\x03" % (colors[num], s)
|
|
|
|
|
|
|
|
def cb_relay_core(irc, source, command, args):
|
|
|
|
"""
|
|
|
|
This function takes Clientbot actions and outputs them to a channel as regular text.
|
|
|
|
"""
|
|
|
|
real_command = command.split('_')[-1]
|
|
|
|
|
|
|
|
relay = world.plugins.get('relay')
|
|
|
|
if irc.pseudoclient and relay:
|
2016-07-24 03:48:27 +02:00
|
|
|
try:
|
|
|
|
sourcename = irc.getFriendlyName(source)
|
|
|
|
except KeyError: # User has left due to /quit
|
|
|
|
sourcename = args['userdata'].nick
|
|
|
|
|
2016-08-09 07:20:31 +02:00
|
|
|
relay_conf = conf.conf.get('relay', {})
|
|
|
|
|
|
|
|
# Be less floody on startup: don't relay non-PRIVMSGs for the first X seconds after connect.
|
2016-08-09 07:23:42 +02:00
|
|
|
startup_delay = relay_conf.get('clientbot_startup_delay', 5)
|
2016-08-09 07:20:31 +02:00
|
|
|
|
2016-07-24 03:48:27 +02:00
|
|
|
# Special case for CTCPs.
|
|
|
|
if real_command == 'MESSAGE':
|
|
|
|
# CTCP action, format accordingly
|
|
|
|
if (not args.get('is_notice')) and args['text'].startswith('\x01ACTION ') and args['text'].endswith('\x01'):
|
|
|
|
args['text'] = args['text'][8:-1]
|
|
|
|
|
|
|
|
real_command = 'ACTION'
|
|
|
|
|
|
|
|
# Other CTCPs are ignored
|
|
|
|
elif args['text'].startswith('\x01'):
|
|
|
|
return
|
|
|
|
elif args.get('is_notice'): # Different syntax for notices
|
|
|
|
real_command = 'NOTICE'
|
2016-08-09 07:20:31 +02:00
|
|
|
elif (time.time() - irc.start_ts) < startup_delay:
|
|
|
|
log.debug('(%s) relay_cb_core: Not relaying %s because of startup delay of %s.', irc.name,
|
|
|
|
real_command, startup_delay)
|
|
|
|
return
|
|
|
|
|
|
|
|
# Try to fetch the format for the given command from the relay:clientbot_styles:$command
|
|
|
|
# key, falling back to one defined in default_styles above, and then nothing if not found
|
|
|
|
# there.
|
|
|
|
text_template = relay_conf.get('clientbot_styles', {}).get(real_command,
|
2016-07-23 21:05:22 +02:00
|
|
|
default_styles.get(real_command, ''))
|
|
|
|
text_template = string.Template(text_template)
|
|
|
|
|
|
|
|
if text_template:
|
2016-07-24 03:48:27 +02:00
|
|
|
# Get the original client that the relay client source was meant for.
|
2016-08-04 21:50:38 +02:00
|
|
|
log.debug('(%s) relay_cb_core: Trying to find original sender (user) for %s', irc.name, source)
|
2016-07-24 03:48:27 +02:00
|
|
|
try:
|
|
|
|
origuser = relay.getOrigUser(irc, source) or args['userdata'].remote
|
|
|
|
except (AttributeError, KeyError):
|
2016-08-04 21:50:38 +02:00
|
|
|
log.debug('(%s) relay_cb_core: Trying to find original sender (server) for %s. serverdata=%s', irc.name, source, args.get('serverdata'))
|
2016-07-24 20:54:14 +02:00
|
|
|
try:
|
2016-08-04 21:50:38 +02:00
|
|
|
origuser = ((args.get('serverdata') or irc.servers[source]).remote,)
|
2016-07-24 20:54:14 +02:00
|
|
|
except (AttributeError, KeyError):
|
|
|
|
return
|
2016-08-04 21:50:38 +02:00
|
|
|
|
|
|
|
log.debug('(%s) relay_cb_core: Original sender found as %s', irc.name, origuser)
|
2016-07-23 21:05:22 +02:00
|
|
|
netname = origuser[0]
|
2016-07-24 07:30:25 +02:00
|
|
|
try: # Try to get the full network name
|
2016-08-22 02:28:26 +02:00
|
|
|
netname = conf.conf['servers'][netname]['netname']
|
2016-07-24 07:30:25 +02:00
|
|
|
except KeyError:
|
|
|
|
pass
|
2016-07-23 21:05:22 +02:00
|
|
|
|
|
|
|
# Figure out where the message is destined to.
|
|
|
|
target = args.get('channel') or args.get('target')
|
|
|
|
if target is None or not utils.isChannel(target):
|
2016-07-24 03:48:27 +02:00
|
|
|
# Quit and nick messages are not channel specific. Figure out all channels that the
|
|
|
|
# sender shares over the relay, and relay them that way.
|
|
|
|
userdata = args.get('userdata') or irc.users.get(source)
|
2016-08-04 21:50:38 +02:00
|
|
|
if not userdata:
|
|
|
|
# No user data given. This was probably some other global event such as SQUIT.
|
|
|
|
userdata = irc.pseudoclient
|
|
|
|
|
2016-07-24 03:48:27 +02:00
|
|
|
channels = [channel for channel in userdata.channels if relay.getRelay((irc.name, channel))]
|
|
|
|
else:
|
|
|
|
# Pluralize the channel so that we can iterate over it.
|
|
|
|
channels = [target]
|
2016-08-04 21:50:38 +02:00
|
|
|
log.debug('(%s) relay_cb_core: Relaying event %s to channels: %s', irc.name, real_command, channels)
|
2016-07-23 21:05:22 +02:00
|
|
|
|
|
|
|
if source in irc.users:
|
2016-07-23 21:25:52 +02:00
|
|
|
try:
|
|
|
|
identhost = irc.getHostmask(source).split('!')[-1]
|
|
|
|
except KeyError: # User got removed due to quit
|
|
|
|
identhost = '%s@%s' % (args['olduser'].ident, args['olduser'].host)
|
2016-07-23 21:05:22 +02:00
|
|
|
# This is specifically spaced so that ident@host is only shown for users that have
|
|
|
|
# one, and not servers.
|
|
|
|
identhost = ' (%s)' % identhost
|
|
|
|
else:
|
|
|
|
identhost = ''
|
|
|
|
|
2016-08-04 21:50:38 +02:00
|
|
|
# $target_nick: Convert the target for kicks, etc. from a UID to a nick
|
2016-07-23 21:25:52 +02:00
|
|
|
if args.get("target") in irc.users:
|
2016-08-04 21:50:38 +02:00
|
|
|
args["target_nick"] = irc.getFriendlyName(args['target'])
|
2016-07-24 03:48:27 +02:00
|
|
|
|
2016-08-04 21:50:38 +02:00
|
|
|
args.update({'netname': netname, 'sender': sourcename, 'sender_identhost': identhost,
|
|
|
|
'colored_sender': color_text(sourcename), 'colored_netname': color_text(netname)})
|
|
|
|
|
2016-07-24 03:48:27 +02:00
|
|
|
for channel in channels:
|
2016-08-09 05:59:15 +02:00
|
|
|
cargs = args.copy() # Copy args list to manipualte them in a channel specific way
|
|
|
|
|
|
|
|
# $nicks / $colored_nicks: used when the event affects multiple users, such as SJOIN or SQUIT.
|
|
|
|
# For SJOIN, this is simply a list of nicks. For SQUIT, this is sent as a dict
|
|
|
|
# mapping channels to lists of nicks, as netsplits aren't channel specific but
|
|
|
|
# still have to be relayed as such.
|
|
|
|
nicklist = args.get('nicks')
|
|
|
|
if nicklist:
|
2016-08-09 07:20:31 +02:00
|
|
|
|
|
|
|
# Get channel-specific nick list if relevent.
|
2016-08-09 05:59:15 +02:00
|
|
|
if type(nicklist) == collections.defaultdict:
|
|
|
|
nicklist = nicklist.get(channel, [])
|
|
|
|
|
2016-08-09 06:05:55 +02:00
|
|
|
# Ignore if no nicks are affected on the channel.
|
|
|
|
if not nicklist:
|
|
|
|
continue
|
|
|
|
|
2016-08-09 05:59:15 +02:00
|
|
|
colored_nicks = [color_text(nick) for nick in nicklist]
|
|
|
|
|
|
|
|
# Join both the nicks and colored_nicks fields into a comma separated string.
|
|
|
|
cargs['nicks'] = ', '.join(nicklist)
|
|
|
|
cargs['colored_nicks'] = ', '.join(colored_nicks)
|
|
|
|
|
|
|
|
text = text_template.safe_substitute(cargs)
|
2016-07-24 03:48:27 +02:00
|
|
|
irc.proto.message(irc.pseudoclient.uid, channel, text)
|
2016-07-23 21:05:22 +02:00
|
|
|
|
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_MESSAGE')
|
2016-07-23 21:25:52 +02:00
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_KICK')
|
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_PART')
|
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_JOIN')
|
2016-07-24 03:48:27 +02:00
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_QUIT')
|
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_NICK')
|
2016-08-04 21:50:38 +02:00
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_SJOIN')
|
|
|
|
utils.add_hook(cb_relay_core, 'CLIENTBOT_SQUIT')
|