2015-09-03 08:24:58 +02:00
|
|
|
"""
|
|
|
|
bots.py: Spawn virtual users/bots on a PyLink server and make them interact
|
|
|
|
with things.
|
|
|
|
"""
|
2016-06-21 03:18:54 +02:00
|
|
|
from pylinkirc import utils
|
2016-12-10 06:41:10 +01:00
|
|
|
from pylinkirc.coremods import permissions
|
2019-07-15 00:12:29 +02:00
|
|
|
from pylinkirc.log import log
|
|
|
|
|
2015-06-07 22:40:18 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def spawnclient(irc, source, args):
|
2015-07-18 07:35:34 +02:00
|
|
|
"""<nick> <ident> <host>
|
|
|
|
|
2017-08-02 16:10:04 +02:00
|
|
|
Spawns the specified client on the PyLink server.
|
2015-07-18 07:35:34 +02:00
|
|
|
Note: this doesn't check the validity of any fields you give it!"""
|
2017-12-22 21:24:58 +01:00
|
|
|
|
|
|
|
if not irc.has_cap('can-spawn-clients'):
|
|
|
|
irc.error("This network does not support client spawning.")
|
|
|
|
return
|
|
|
|
|
2017-08-02 16:24:23 +02:00
|
|
|
permissions.check_permissions(irc, source, ['bots.spawnclient'])
|
2015-06-07 22:40:18 +02:00
|
|
|
try:
|
|
|
|
nick, ident, host = args[:3]
|
|
|
|
except ValueError:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Not enough arguments. Needs 3: nick, user, host.")
|
2015-06-07 22:40:18 +02:00
|
|
|
return
|
2017-07-01 06:30:20 +02:00
|
|
|
irc.spawn_client(nick, ident, host, manipulatable=True)
|
2017-03-10 05:49:38 +01:00
|
|
|
irc.reply("Done.")
|
2015-06-07 22:40:18 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
2015-07-11 01:43:03 +02:00
|
|
|
def quit(irc, source, args):
|
2015-07-18 07:35:34 +02:00
|
|
|
"""<target> [<reason>]
|
|
|
|
|
2017-08-02 16:10:04 +02:00
|
|
|
Quits the PyLink client with nick <target>, if one exists."""
|
2017-08-02 16:24:23 +02:00
|
|
|
permissions.check_permissions(irc, source, ['bots.quit'])
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2015-06-07 22:40:18 +02:00
|
|
|
try:
|
2015-06-08 04:31:56 +02:00
|
|
|
nick = args[0]
|
2015-06-07 22:40:18 +02:00
|
|
|
except IndexError:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Not enough arguments. Needs 1-2: nick, reason (optional).")
|
2015-06-07 22:40:18 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2019-06-16 19:45:05 +02:00
|
|
|
u = irc.nick_to_uid(nick, filterfunc=irc.is_internal_client)
|
|
|
|
if u is None:
|
|
|
|
irc.error("Unknown user %r" % nick)
|
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-08-02 16:09:48 +02:00
|
|
|
if irc.pseudoclient.uid == u:
|
|
|
|
irc.error("Cannot quit the main PyLink client!")
|
|
|
|
return
|
|
|
|
|
2019-06-16 19:45:05 +02:00
|
|
|
quitmsg = ' '.join(args[1:]) or 'Client Quit'
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-30 08:01:39 +02:00
|
|
|
if not irc.is_manipulatable_client(u):
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Cannot force quit a protected PyLink services client.")
|
2015-09-18 04:07:16 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-25 11:07:24 +02:00
|
|
|
irc.quit(u, quitmsg)
|
2017-03-10 05:49:38 +01:00
|
|
|
irc.reply("Done.")
|
2017-06-30 08:01:39 +02:00
|
|
|
irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
|
2015-06-07 22:40:18 +02:00
|
|
|
|
|
|
|
def joinclient(irc, source, args):
|
2017-02-14 01:59:54 +01:00
|
|
|
"""[<target>] <channel1>[,<channel2>,<channel3>,...]
|
2015-07-18 07:35:34 +02:00
|
|
|
|
2017-08-02 16:10:04 +02:00
|
|
|
Joins <target>, the nick of a PyLink client, to a comma-separated list of channels.
|
2017-02-14 01:59:54 +01:00
|
|
|
If <target> is not given, it defaults to the main PyLink client.
|
|
|
|
|
|
|
|
For the channel arguments, prefixes can also be specified to join the given client with
|
|
|
|
(e.g. @#channel will join the client with op, while ~@#channel will join it with +qo.
|
|
|
|
"""
|
2018-07-12 07:15:36 +02:00
|
|
|
permissions.check_permissions(irc, source, ['bots.join', 'bots.joinclient'])
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2015-06-07 22:40:18 +02:00
|
|
|
try:
|
2016-02-28 04:05:06 +01:00
|
|
|
# Check if the first argument is an existing PyLink client. If it is not,
|
|
|
|
# then assume that the first argument was actually the channels being joined.
|
2019-06-16 19:45:05 +02:00
|
|
|
u = irc.nick_to_uid(args[0], filterfunc=irc.is_internal_client)
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2019-06-16 19:45:05 +02:00
|
|
|
if u is None: # First argument isn't one of our clients
|
2015-06-07 22:40:18 +02:00
|
|
|
raise IndexError
|
2016-02-28 04:05:06 +01:00
|
|
|
|
|
|
|
clist = args[1]
|
2019-06-16 19:45:05 +02:00
|
|
|
except IndexError: # No valid nick was given; shift arguments one to the left.
|
2016-02-28 04:05:06 +01:00
|
|
|
u = irc.pseudoclient.uid
|
|
|
|
try:
|
|
|
|
clist = args[0]
|
|
|
|
except IndexError:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels.")
|
2016-02-28 04:05:06 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
clist = clist.split(',')
|
|
|
|
if not clist:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("No valid channels given.")
|
2015-06-07 22:40:18 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-30 08:01:39 +02:00
|
|
|
if not (irc.is_manipulatable_client(u) or irc.get_service_bot(u)):
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Cannot force join a protected PyLink services client.")
|
2015-09-18 04:07:16 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-02-14 01:59:54 +01:00
|
|
|
prefix_to_mode = {v: k for k, v in irc.prefixmodes.items()}
|
2015-06-08 04:31:56 +02:00
|
|
|
for channel in clist:
|
2017-02-14 01:59:54 +01:00
|
|
|
real_channel = channel.lstrip(''.join(prefix_to_mode))
|
|
|
|
# XXX we need a better way to do this, but only the other option I can think of is regex...
|
|
|
|
prefixes = channel[:len(channel)-len(real_channel)]
|
|
|
|
joinmodes = ''.join(prefix_to_mode[prefix] for prefix in prefixes)
|
|
|
|
|
2017-08-31 06:20:19 +02:00
|
|
|
if not irc.is_channel(real_channel):
|
2017-02-14 01:59:54 +01:00
|
|
|
irc.error("Invalid channel name %r." % real_channel)
|
2015-06-08 04:31:56 +02:00
|
|
|
return
|
2017-02-14 01:59:54 +01:00
|
|
|
|
|
|
|
# join() doesn't support prefixes.
|
|
|
|
if prefixes:
|
2017-06-25 11:07:24 +02:00
|
|
|
irc.sjoin(irc.sid, real_channel, [(joinmodes, u)])
|
2017-02-14 01:59:54 +01:00
|
|
|
else:
|
2017-06-25 11:07:24 +02:00
|
|
|
irc.join(u, real_channel)
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-12-22 21:11:21 +01:00
|
|
|
try:
|
|
|
|
modes = irc.channels[real_channel].modes
|
|
|
|
except KeyError:
|
|
|
|
modes = []
|
|
|
|
|
2019-06-16 19:45:05 +02:00
|
|
|
# Signal the join to other plugins
|
2017-06-30 08:01:39 +02:00
|
|
|
irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': real_channel, 'users': [u],
|
2017-12-22 21:11:21 +01:00
|
|
|
'modes': modes, 'parse_as': 'JOIN'}])
|
2017-03-10 05:49:38 +01:00
|
|
|
irc.reply("Done.")
|
2015-07-09 07:50:19 +02:00
|
|
|
utils.add_cmd(joinclient, name='join')
|
2015-06-08 04:31:56 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
2015-07-09 07:50:19 +02:00
|
|
|
def nick(irc, source, args):
|
2016-02-28 04:05:06 +01:00
|
|
|
"""[<target>] <newnick>
|
2015-07-18 07:35:34 +02:00
|
|
|
|
2017-08-02 16:10:04 +02:00
|
|
|
Changes the nick of <target>, a PyLink client, to <newnick>. If <target> is not given, it defaults to the main PyLink client."""
|
2016-12-10 06:41:10 +01:00
|
|
|
|
2017-08-02 16:24:23 +02:00
|
|
|
permissions.check_permissions(irc, source, ['bots.nick'])
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2015-06-08 04:31:56 +02:00
|
|
|
try:
|
|
|
|
nick = args[0]
|
|
|
|
newnick = args[1]
|
|
|
|
except IndexError:
|
2016-02-28 04:05:06 +01:00
|
|
|
try:
|
|
|
|
nick = irc.pseudoclient.nick
|
|
|
|
newnick = args[0]
|
|
|
|
except IndexError:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Not enough arguments. Needs 1-2: nick (optional), newnick.")
|
2016-02-28 04:05:06 +01:00
|
|
|
return
|
2019-06-16 19:45:05 +02:00
|
|
|
u = irc.nick_to_uid(nick, filterfunc=irc.is_internal_client)
|
2016-02-28 04:05:06 +01:00
|
|
|
|
|
|
|
if newnick in ('0', u): # Allow /nick 0 to work
|
2015-07-09 07:50:19 +02:00
|
|
|
newnick = u
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-11-08 04:19:00 +01:00
|
|
|
elif not irc.is_nick(newnick):
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error('Invalid nickname %r.' % newnick)
|
2015-07-09 07:50:19 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-30 08:01:39 +02:00
|
|
|
elif not (irc.is_manipulatable_client(u) or irc.get_service_bot(u)):
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Cannot force nick changes for a protected PyLink services client.")
|
2015-09-18 04:07:16 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-25 11:07:24 +02:00
|
|
|
irc.nick(u, newnick)
|
2017-03-10 05:49:38 +01:00
|
|
|
irc.reply("Done.")
|
2019-06-16 19:45:05 +02:00
|
|
|
# Signal the nick change to other plugins
|
2017-06-30 08:01:39 +02:00
|
|
|
irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
|
2015-06-08 04:31:56 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
2015-07-09 07:50:19 +02:00
|
|
|
def part(irc, source, args):
|
2016-02-28 04:05:06 +01:00
|
|
|
"""[<target>] <channel1>,[<channel2>],... [<reason>]
|
2015-07-18 07:35:34 +02:00
|
|
|
|
2017-08-02 16:10:04 +02:00
|
|
|
Parts <target>, the nick of a PyLink client, from a comma-separated list of channels. If <target> is not given, it defaults to the main PyLink client."""
|
2017-08-02 16:24:23 +02:00
|
|
|
permissions.check_permissions(irc, source, ['bots.part'])
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2015-06-08 04:31:56 +02:00
|
|
|
try:
|
|
|
|
nick = args[0]
|
2016-02-28 04:05:06 +01:00
|
|
|
clist = args[1]
|
|
|
|
# For the part message, join all remaining arguments into one text string
|
2015-06-08 04:31:56 +02:00
|
|
|
reason = ' '.join(args[2:])
|
2016-02-28 04:05:06 +01:00
|
|
|
|
|
|
|
# First, check if the first argument is an existing PyLink client. If it is not,
|
|
|
|
# then assume that the first argument was actually the channels being parted.
|
2019-06-16 19:45:05 +02:00
|
|
|
u = irc.nick_to_uid(nick, filterfunc=irc.is_internal_client)
|
|
|
|
if u is None: # First argument isn't one of our clients
|
2016-02-28 04:05:06 +01:00
|
|
|
raise IndexError
|
|
|
|
|
|
|
|
except IndexError: # No nick was given; shift arguments one to the left.
|
|
|
|
u = irc.pseudoclient.uid
|
|
|
|
|
|
|
|
try:
|
|
|
|
clist = args[0]
|
|
|
|
except IndexError:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels.")
|
2016-02-28 04:05:06 +01:00
|
|
|
return
|
|
|
|
reason = ' '.join(args[1:])
|
|
|
|
|
|
|
|
clist = clist.split(',')
|
|
|
|
if not clist:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("No valid channels given.")
|
2015-06-07 22:40:18 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-30 08:01:39 +02:00
|
|
|
if not (irc.is_manipulatable_client(u) or irc.get_service_bot(u)):
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Cannot force part a protected PyLink services client.")
|
2015-09-18 04:07:16 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2015-06-07 22:40:18 +02:00
|
|
|
for channel in clist:
|
2017-08-31 06:20:19 +02:00
|
|
|
if not irc.is_channel(channel):
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error("Invalid channel name %r." % channel)
|
2015-06-07 22:40:18 +02:00
|
|
|
return
|
2017-06-25 11:07:24 +02:00
|
|
|
irc.part(u, channel, reason)
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-03-10 05:49:38 +01:00
|
|
|
irc.reply("Done.")
|
2017-06-30 08:01:39 +02:00
|
|
|
irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
|
2015-06-08 04:31:56 +02:00
|
|
|
|
2015-08-15 13:51:32 +02:00
|
|
|
def msg(irc, source, args):
|
2016-02-28 04:05:06 +01:00
|
|
|
"""[<source>] <target> <text>
|
2015-08-15 13:51:32 +02:00
|
|
|
|
2017-08-02 16:10:04 +02:00
|
|
|
Sends message <text> from <source>, where <source> is the nick of a PyLink client. If <source> is not given, it defaults to the main PyLink client."""
|
2017-08-02 16:24:23 +02:00
|
|
|
permissions.check_permissions(irc, source, ['bots.msg'])
|
2016-02-28 04:05:06 +01:00
|
|
|
|
|
|
|
# Because we want the source nick to be optional, this argument parsing gets a bit tricky.
|
2015-08-15 13:51:32 +02:00
|
|
|
try:
|
2016-02-28 04:05:06 +01:00
|
|
|
msgsource = args[0]
|
|
|
|
target = args[1]
|
|
|
|
text = ' '.join(args[2:])
|
|
|
|
|
|
|
|
# First, check if the first argument is an existing PyLink client. If it is not,
|
|
|
|
# then assume that the first argument was actually the message TARGET.
|
2019-06-16 19:45:05 +02:00
|
|
|
sourceuid = irc.nick_to_uid(msgsource, filterfunc=irc.is_internal_client)
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2019-06-16 19:45:05 +02:00
|
|
|
if sourceuid is None or not text: # First argument isn't one of our clients
|
2016-02-28 04:05:06 +01:00
|
|
|
raise IndexError
|
2019-06-16 19:45:05 +02:00
|
|
|
|
2015-08-15 13:51:32 +02:00
|
|
|
except IndexError:
|
2016-02-28 04:05:06 +01:00
|
|
|
try:
|
|
|
|
sourceuid = irc.pseudoclient.uid
|
|
|
|
target = args[0]
|
|
|
|
text = ' '.join(args[1:])
|
|
|
|
except IndexError:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error('Not enough arguments. Needs 2-3: source nick (optional), target, text.')
|
2016-02-28 04:05:06 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
if not text:
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error('No text given.')
|
2015-08-15 13:51:32 +02:00
|
|
|
return
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2019-06-16 19:45:05 +02:00
|
|
|
try:
|
|
|
|
int_u = int(target)
|
|
|
|
except:
|
|
|
|
int_u = None
|
|
|
|
|
|
|
|
if int_u and int_u in irc.users:
|
|
|
|
real_target = int_u # Some protocols use numeric UIDs
|
|
|
|
elif target in irc.users:
|
|
|
|
real_target = target
|
|
|
|
elif not irc.is_channel(target):
|
|
|
|
# Convert nick of the message target to a UID, if the target isn't a channel or UID
|
|
|
|
potential_targets = irc.nick_to_uid(target, multi=True)
|
|
|
|
if not potential_targets: # Unknown target user, if target isn't a valid channel name
|
2016-11-19 07:11:16 +01:00
|
|
|
irc.error('Unknown user %r.' % target)
|
2015-08-15 13:51:32 +02:00
|
|
|
return
|
2019-06-16 19:45:05 +02:00
|
|
|
elif len(potential_targets) > 1:
|
|
|
|
irc.error('Multiple users with the nick %r found: please select the right UID: %s' % (target, str(potential_targets)))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
real_target = potential_targets[0]
|
2015-08-15 13:51:32 +02:00
|
|
|
else:
|
|
|
|
real_target = target
|
2016-02-28 04:05:06 +01:00
|
|
|
|
2017-06-25 11:07:24 +02:00
|
|
|
irc.message(sourceuid, real_target, text)
|
2017-03-10 05:49:38 +01:00
|
|
|
irc.reply("Done.")
|
2017-06-30 08:01:39 +02:00
|
|
|
irc.call_hooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])
|
2017-07-11 06:59:29 +02:00
|
|
|
utils.add_cmd(msg, aliases=('say',))
|