""" bots.py: Spawn virtual users/bots on a PyLink server and make them interact with things. """ from pylinkirc import utils from pylinkirc.log import log from pylinkirc.coremods import permissions @utils.add_cmd def spawnclient(irc, source, args): """ Spawns the specified client on the PyLink server. Note: this doesn't check the validity of any fields you give it!""" if not irc.has_cap('can-spawn-clients'): irc.error("This network does not support client spawning.") return permissions.check_permissions(irc, source, ['bots.spawnclient']) try: nick, ident, host = args[:3] except ValueError: irc.error("Not enough arguments. Needs 3: nick, user, host.") return irc.spawn_client(nick, ident, host, manipulatable=True) irc.reply("Done.") @utils.add_cmd def quit(irc, source, args): """ [] Quits the PyLink client with nick , if one exists.""" permissions.check_permissions(irc, source, ['bots.quit']) try: nick = args[0] except IndexError: irc.error("Not enough arguments. Needs 1-2: nick, reason (optional).") return u = irc.nick_to_uid(nick) if irc.pseudoclient.uid == u: irc.error("Cannot quit the main PyLink client!") return quitmsg = ' '.join(args[1:]) or 'Client Quit' if not irc.is_manipulatable_client(u): irc.error("Cannot force quit a protected PyLink services client.") return irc.quit(u, quitmsg) irc.reply("Done.") irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}]) def joinclient(irc, source, args): """[] [,,,...] Joins , the nick of a PyLink client, to a comma-separated list of channels. If 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. """ permissions.check_permissions(irc, source, ['bots.joinclient']) try: # 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. u = irc.nick_to_uid(args[0]) if not irc.is_internal_client(u): # First argument isn't one of our clients raise IndexError clist = args[1] except IndexError: # No nick was given; shift arguments one to the left. u = irc.pseudoclient.uid try: clist = args[0] except IndexError: irc.error("Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels.") return clist = clist.split(',') if not clist: irc.error("No valid channels given.") return if not (irc.is_manipulatable_client(u) or irc.get_service_bot(u)): irc.error("Cannot force join a protected PyLink services client.") return prefix_to_mode = {v: k for k, v in irc.prefixmodes.items()} for channel in clist: 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) if not irc.is_channel(real_channel): irc.error("Invalid channel name %r." % real_channel) return # join() doesn't support prefixes. if prefixes: irc.sjoin(irc.sid, real_channel, [(joinmodes, u)]) else: irc.join(u, real_channel) try: modes = irc.channels[real_channel].modes except KeyError: modes = [] # Call a join hook manually so other plugins like relay can understand it. irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': real_channel, 'users': [u], 'modes': modes, 'parse_as': 'JOIN'}]) irc.reply("Done.") utils.add_cmd(joinclient, name='join') @utils.add_cmd def nick(irc, source, args): """[] Changes the nick of , a PyLink client, to . If is not given, it defaults to the main PyLink client.""" permissions.check_permissions(irc, source, ['bots.nick']) try: nick = args[0] newnick = args[1] except IndexError: try: nick = irc.pseudoclient.nick newnick = args[0] except IndexError: irc.error("Not enough arguments. Needs 1-2: nick (optional), newnick.") return u = irc.nick_to_uid(nick) if newnick in ('0', u): # Allow /nick 0 to work newnick = u elif not irc.is_nick(newnick): irc.error('Invalid nickname %r.' % newnick) return elif not (irc.is_manipulatable_client(u) or irc.get_service_bot(u)): irc.error("Cannot force nick changes for a protected PyLink services client.") return irc.nick(u, newnick) irc.reply("Done.") # Ditto above: manually send a NICK change hook payload to other plugins. irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}]) @utils.add_cmd def part(irc, source, args): """[] ,[],... [] Parts , the nick of a PyLink client, from a comma-separated list of channels. If is not given, it defaults to the main PyLink client.""" permissions.check_permissions(irc, source, ['bots.part']) try: nick = args[0] clist = args[1] # For the part message, join all remaining arguments into one text string reason = ' '.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 channels being parted. u = irc.nick_to_uid(nick) if not irc.is_internal_client(u): # First argument isn't one of our clients raise IndexError except IndexError: # No nick was given; shift arguments one to the left. u = irc.pseudoclient.uid try: clist = args[0] except IndexError: irc.error("Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels.") return reason = ' '.join(args[1:]) clist = clist.split(',') if not clist: irc.error("No valid channels given.") return if not (irc.is_manipulatable_client(u) or irc.get_service_bot(u)): irc.error("Cannot force part a protected PyLink services client.") return for channel in clist: if not irc.is_channel(channel): irc.error("Invalid channel name %r." % channel) return irc.part(u, channel, reason) irc.reply("Done.") irc.call_hooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}]) def msg(irc, source, args): """[] Sends message from , where is the nick of a PyLink client. If is not given, it defaults to the main PyLink client.""" permissions.check_permissions(irc, source, ['bots.msg']) # Because we want the source nick to be optional, this argument parsing gets a bit tricky. try: 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. sourceuid = irc.nick_to_uid(msgsource) if not irc.is_internal_client(sourceuid): # First argument isn't one of our clients raise IndexError if not text: raise IndexError except IndexError: try: sourceuid = irc.pseudoclient.uid target = args[0] text = ' '.join(args[1:]) except IndexError: irc.error('Not enough arguments. Needs 2-3: source nick (optional), target, text.') return if not text: irc.error('No text given.') return if not irc.is_channel(target): # Convert nick of the message target to a UID, if the target isn't a channel real_target = irc.nick_to_uid(target) if real_target is None: # Unknown target user, if target isn't a valid channel name irc.error('Unknown user %r.' % target) return else: real_target = target irc.message(sourceuid, real_target, text) irc.reply("Done.") irc.call_hooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}]) utils.add_cmd(msg, aliases=('say',))