From 54dc51aed4753691530b2ff056dac20ff2ac7c72 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 27 Feb 2016 19:05:06 -0800 Subject: [PATCH] bots: make source client names optional (Closes #161) --- plugins/bots.py | 134 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 31 deletions(-) diff --git a/plugins/bots.py b/plugins/bots.py index 5b861a3..e47559d 100644 --- a/plugins/bots.py +++ b/plugins/bots.py @@ -30,6 +30,7 @@ def quit(irc, source, args): Admin-only. Quits the PyLink client with nick , if one exists.""" utils.checkAuthenticated(irc, source, allowOper=False) + try: nick = args[0] except IndexError: @@ -38,36 +39,57 @@ def quit(irc, source, args): if irc.pseudoclient.uid == irc.nickToUid(nick): irc.reply("Error: Cannot quit the main PyLink PseudoClient!") return + u = irc.nickToUid(nick) + quitmsg = ' '.join(args[1:]) or 'Client Quit' + if not utils.isManipulatableClient(irc, u): irc.reply("Error: Cannot force quit a protected PyLink services client.") return + irc.proto.quit(u, quitmsg) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}]) def joinclient(irc, source, args): - """ ,[], etc. + """[] ,[], etc. - Admin-only. Joins , the nick of a PyLink client, to a comma-separated list of channels.""" + Admin-only. 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.""" utils.checkAuthenticated(irc, source, allowOper=False) + try: - nick = args[0] - clist = args[1].split(',') - if not clist: + # 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.nickToUid(args[0]) + + if not irc.isInternalClient(u): # First argument isn't one of our clients raise IndexError - except IndexError: - irc.reply("Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") + + 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.reply("Error: Not enough arguments. Needs 1-2: nick (optional), comma separated list of channels.") + return + + clist = clist.split(',') + if not clist: + irc.reply("Error: No valid channels given.") return - u = irc.nickToUid(nick) + if not utils.isManipulatableClient(irc, u): irc.reply("Error: Cannot force join a protected PyLink services client.") return + for channel in clist: if not utils.isChannel(channel): irc.reply("Error: Invalid channel name %r." % channel) return irc.proto.join(u, channel) + + # Call a join hook manually so other plugins like relay can understand it. irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u], 'modes': irc.channels[channel].modes, 'parse_as': 'JOIN'}]) @@ -75,76 +97,126 @@ utils.add_cmd(joinclient, name='join') @utils.add_cmd def nick(irc, source, args): - """ + """[] - Admin-only. Changes the nick of , a PyLink client, to .""" + Admin-only. Changes the nick of , a PyLink client, to . If is not given, it defaults to the main PyLink client.""" utils.checkAuthenticated(irc, source, allowOper=False) + try: nick = args[0] newnick = args[1] except IndexError: - irc.reply("Error: Not enough arguments. Needs 2: nick, newnick.") - return + try: + nick = irc.pseudoclient.nick + newnick = args[0] + except IndexError: + irc.reply("Error: Not enough arguments. Needs 1-2: nick (optional), newnick.") + return u = irc.nickToUid(nick) - if newnick in ('0', u): + + if newnick in ('0', u): # Allow /nick 0 to work newnick = u + elif not utils.isNick(newnick): irc.reply('Error: Invalid nickname %r.' % newnick) return + elif not utils.isManipulatableClient(irc, u): irc.reply("Error: Cannot force nick changes for a protected PyLink services client.") return + irc.proto.nick(u, newnick) + # Ditto above: manually send a NICK change hook payload to other plugins. irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}]) @utils.add_cmd def part(irc, source, args): - """ ,[],... [] + """[] ,[],... [] - Admin-only. Parts , the nick of a PyLink client, from a comma-separated list of channels.""" + Admin-only. 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.""" utils.checkAuthenticated(irc, source, allowOper=False) + try: nick = args[0] - clist = args[1].split(',') + clist = args[1] + # For the part message, join all remaining arguments into one text string reason = ' '.join(args[2:]) - except IndexError: - irc.reply("Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") + + # 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.nickToUid(nick) + if not irc.isInternalClient(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.reply("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.reply("Error: No valid channels given.") return - u = irc.nickToUid(nick) + if not utils.isManipulatableClient(irc, u): irc.reply("Error: Cannot force part a protected PyLink services client.") return + for channel in clist: if not utils.isChannel(channel): irc.reply("Error: Invalid channel name %r." % channel) return irc.proto.part(u, channel, reason) + irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}]) @utils.add_cmd def msg(irc, source, args): - """ + """[] - Admin-only. Sends message from , where is the nick of a PyLink client.""" + Admin-only. Sends message from , where is the nick of a PyLink client. If is not given, it defaults to the main PyLink client.""" utils.checkAuthenticated(irc, source, allowOper=False) + + # Because we want the source nick to be optional, this argument parsing gets a bit tricky. try: - msgsource, target, text = args[0], args[1], ' '.join(args[2:]) + 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.nickToUid(msgsource) + if not irc.isInternalClient(sourceuid): # First argument isn't one of our clients + raise IndexError + + if not text: + raise IndexError except IndexError: - irc.reply('Error: Not enough arguments. Needs 3: source nick, target, text.') - return - sourceuid = irc.nickToUid(msgsource) - if not sourceuid: - irc.reply('Error: Unknown user %r.' % msgsource) + try: + sourceuid = irc.pseudoclient.uid + target = args[0] + text = ' '.join(args[1:]) + except IndexError: + irc.reply('Error: Not enough arguments. Needs 3: source nick, target, text.') + return + + if not text: + irc.reply('Error: No text given.') return + if not utils.isChannel(target): + # Convert nick of the message target to a UID, if the target isn't a channel real_target = irc.nickToUid(target) - if real_target is None: + if real_target is None: # Unknown target user, if target isn't a valid channel name irc.reply('Error: Unknown user %r.' % target) return else: real_target = target - if not text: - irc.reply('Error: No text given.') - return + irc.proto.message(sourceuid, real_target, text) irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])