From 6034333682414210564724f039fc617e45b94e71 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 18 Oct 2015 10:09:31 -0700 Subject: [PATCH 01/17] commands: actually break when identify is sent in a channel --- plugins/commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/commands.py b/plugins/commands.py index 9af2637..1a4dce4 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -32,6 +32,7 @@ def identify(irc, source, args): if utils.isChannel(irc.called_by): irc.msg(irc.called_by, 'Error: This command must be sent in private. ' '(Would you really type a password inside a public channel?)') + return try: username, password = args[0], args[1] except IndexError: From 5327de9317c84518504ffb2274cb0c0c22c21c6f Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 18 Oct 2015 10:25:59 -0700 Subject: [PATCH 02/17] relay: fix local kicks not quitting users on remote networks when the target doesn't share any channels as a result Closes #120. --- plugins/relay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 8ffe7ba..affd071 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -822,8 +822,8 @@ def handle_kick(irc, source, command, args): remoteirc.proto.kickServer(rsid, remotechan, real_target, text) # If the target isn't on any channels, quit them. - if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels: - del relayusers[origuser][remoteirc.name] + if remoteirc != irc and (not remoteirc.users[real_target].channels) and not origuser: + del relayusers[(irc.name, target)][remoteirc.name] remoteirc.proto.quitClient(real_target, 'Left all shared channels.') if origuser and not irc.users[target].channels: From 1b8f1ff95e17bb7dbf889f304a23ee641942094b Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 18 Oct 2015 12:49:13 -0700 Subject: [PATCH 03/17] commands: s/public channel/channel/ --- plugins/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/commands.py b/plugins/commands.py index 1a4dce4..34c7218 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -31,7 +31,7 @@ def identify(irc, source, args): Logs in to PyLink using the configured administrator account.""" if utils.isChannel(irc.called_by): irc.msg(irc.called_by, 'Error: This command must be sent in private. ' - '(Would you really type a password inside a public channel?)') + '(Would you really type a password inside a channel?)') return try: username, password = args[0], args[1] From d14cf3c7cfbcf45dbf9b5058af59083771bd761d Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 18:22:29 -0700 Subject: [PATCH 04/17] Move (un|re)?load, identify commands to coreplugin This is so the commands plugin, which includes other generic (but not essential) commands, can be more easily reloaded. --- coreplugin.py | 153 +++++++++++++++++++++++++++++++++++++++++++- plugins/commands.py | 150 ------------------------------------------- 2 files changed, 152 insertions(+), 151 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index 7a93138..aa2617c 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -1,4 +1,7 @@ -## coreplugin.py - Core PyLink plugin +# coreplugin.py - Implements core PyLink functions as a plugin + +import gc +import sys import utils from log import log @@ -107,3 +110,151 @@ def handle_mode(irc, source, command, args): if ('-o', None) in modes and (target == irc.pseudoclient.uid or not utils.isManipulatableClient(irc, target)): irc.proto.modeServer(irc.sid, target, {('+o', None)}) utils.add_hook(handle_mode, 'MODE') + +# Essential, core commands go here so that the "commands" plugin with less-important, +# but still generic functions can be reloaded. + +@utils.add_cmd +def identify(irc, source, args): + """ + + Logs in to PyLink using the configured administrator account.""" + if utils.isChannel(irc.called_by): + irc.msg(irc.called_by, 'Error: This command must be sent in private. ' + '(Would you really type a password inside a channel?)') + return + try: + username, password = args[0], args[1] + except IndexError: + irc.msg(source, 'Error: Not enough arguments.') + return + # Usernames are case-insensitive, passwords are NOT. + if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']: + realuser = irc.conf['login']['user'] + irc.users[source].identified = realuser + irc.msg(source, 'Successfully logged in as %s.' % realuser) + log.info("(%s) Successful login to %r by %s.", + irc.name, username, utils.getHostmask(irc, source)) + else: + irc.msg(source, 'Error: Incorrect credentials.') + u = irc.users[source] + log.warning("(%s) Failed login to %r from %s.", + irc.name, username, utils.getHostmask(irc, source)) + +@utils.add_cmd +def shutdown(irc, source, args): + """takes no arguments. + + Exits PyLink by disconnecting all networks.""" + utils.checkAuthenticated(irc, source, allowOper=False) + u = irc.users[source] + log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick, + u.ident, u.host) + for ircobj in world.networkobjects.values(): + # Disable auto-connect first by setting the time to negative. + ircobj.serverdata['autoconnect'] = -1 + ircobj.aborted.set() + +def load(irc, source, args): + """. + + Loads a plugin from the plugin folder.""" + utils.checkAuthenticated(irc, source, allowOper=False) + try: + name = args[0] + except IndexError: + irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + return + if name in world.plugins: + irc.msg(irc.called_by, "Error: %r is already loaded." % name) + return + try: + world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder) + except ImportError as e: + if str(e) == ('No module named %r' % name): + log.exception('Failed to load plugin %r: The plugin could not be found.', name) + else: + log.exception('Failed to load plugin %r: ImportError.', name) + raise + else: + if hasattr(pl, 'main'): + log.debug('Calling main() function of plugin %r', pl) + pl.main(irc) + irc.msg(irc.called_by, "Loaded plugin %r." % name) +utils.add_cmd(load) + +def unload(irc, source, args): + """. + + Unloads a currently loaded plugin.""" + utils.checkAuthenticated(irc, source, allowOper=False) + try: + name = args[0] + except IndexError: + irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + return + if name in world.plugins: + pl = world.plugins[name] + log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl)) + # Remove any command functions set by the plugin. + for cmdname, cmdfuncs in world.commands.copy().items(): + log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs) + for cmdfunc in cmdfuncs: + log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__) + if cmdfunc.__module__ == name: + log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname) + world.commands[cmdname].remove(cmdfunc) + # If the cmdfunc list is empty, remove it. + if not cmdfuncs: + log.debug("Removing world.commands[%s] (it's empty now)", cmdname) + del world.commands[cmdname] + + # Remove any command hooks set by the plugin. + for hookname, hookfuncs in world.hooks.copy().items(): + for hookfunc in hookfuncs: + if hookfunc.__module__ == name: + world.hooks[hookname].remove(hookfunc) + # If the hookfuncs list is empty, remove it. + if not hookfuncs: + del world.hooks[hookname] + + # Remove whois handlers too. + for f in world.whois_handlers: + if f.__module__ == name: + world.whois_handlers.remove(f) + + # Call the die() function in the plugin, if present. + if hasattr(pl, 'die'): + try: + pl.die(irc) + except: # But don't allow it to crash the server. + log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl) + + # Delete it from memory (hopefully). + del world.plugins[name] + if name in sys.modules: + del sys.modules[name] + if name in globals(): + del globals()[name] + + # Garbage collect. + gc.collect() + + irc.msg(irc.called_by, "Unloaded plugin %r." % name) + return True # We succeeded, make it clear (this status is used by reload() below) + else: + irc.msg(irc.called_by, "Unknown plugin %r." % name) +utils.add_cmd(unload) + +@utils.add_cmd +def reload(irc, source, args): + """. + + Loads a plugin from the plugin folder.""" + try: + name = args[0] + except IndexError: + irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + return + if unload(irc, source, args): + load(irc, source, args) diff --git a/plugins/commands.py b/plugins/commands.py index 34c7218..9b3fab9 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -2,8 +2,6 @@ import sys import os from time import ctime -import itertools -import gc sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import utils @@ -24,33 +22,6 @@ def status(irc, source, args): irc.msg(irc.called_by, 'You are not identified as anyone.') irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source))) -@utils.add_cmd -def identify(irc, source, args): - """ - - Logs in to PyLink using the configured administrator account.""" - if utils.isChannel(irc.called_by): - irc.msg(irc.called_by, 'Error: This command must be sent in private. ' - '(Would you really type a password inside a channel?)') - return - try: - username, password = args[0], args[1] - except IndexError: - irc.msg(source, 'Error: Not enough arguments.') - return - # Usernames are case-insensitive, passwords are NOT. - if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']: - realuser = irc.conf['login']['user'] - irc.users[source].identified = realuser - irc.msg(source, 'Successfully logged in as %s.' % realuser) - log.info("(%s) Successful login to %r by %s.", - irc.name, username, utils.getHostmask(irc, source)) - else: - irc.msg(source, 'Error: Incorrect credentials.') - u = irc.users[source] - log.warning("(%s) Failed login to %r from %s.", - irc.name, username, utils.getHostmask(irc, source)) - def listcommands(irc, source, args): """takes no arguments. @@ -179,20 +150,6 @@ def showchan(irc, source, args): f('\x02User list\x02: %s' % ' '.join(nicklist[:20])) nicklist = nicklist[20:] -@utils.add_cmd -def shutdown(irc, source, args): - """takes no arguments. - - Exits PyLink by disconnecting all networks.""" - utils.checkAuthenticated(irc, source, allowOper=False) - u = irc.users[source] - log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick, - u.ident, u.host) - for ircobj in world.networkobjects.values(): - # Disable auto-connect first by setting the time to negative. - ircobj.serverdata['autoconnect'] = -1 - ircobj.aborted.set() - @utils.add_cmd def version(irc, source, args): """takes no arguments. @@ -208,113 +165,6 @@ def echo(irc, source, args): Echoes the text given.""" irc.msg(irc.called_by, ' '.join(args)) -def load(irc, source, args): - """. - - Loads a plugin from the plugin folder.""" - utils.checkAuthenticated(irc, source, allowOper=False) - try: - name = args[0] - except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") - return - if name in world.plugins: - irc.msg(irc.called_by, "Error: %r is already loaded." % name) - return - try: - world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder) - except ImportError as e: - if str(e) == ('No module named %r' % name): - log.exception('Failed to load plugin %r: The plugin could not be found.', name) - else: - log.exception('Failed to load plugin %r: ImportError.', name) - raise - else: - if hasattr(pl, 'main'): - log.debug('Calling main() function of plugin %r', pl) - pl.main(irc) - irc.msg(irc.called_by, "Loaded plugin %r." % name) -utils.add_cmd(load) - -def unload(irc, source, args): - """. - - Unloads a currently loaded plugin.""" - utils.checkAuthenticated(irc, source, allowOper=False) - try: - name = args[0] - except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") - return - if name == 'commands': - irc.msg(irc.called_by, "Error: Cannot unload the commands plugin!") - return - elif name in world.plugins: - pl = world.plugins[name] - log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl)) - # Remove any command functions set by the plugin. - for cmdname, cmdfuncs in world.commands.copy().items(): - log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs) - for cmdfunc in cmdfuncs: - log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__) - if cmdfunc.__module__ == name: - log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname) - world.commands[cmdname].remove(cmdfunc) - # If the cmdfunc list is empty, remove it. - if not cmdfuncs: - log.debug("Removing world.commands[%s] (it's empty now)", cmdname) - del world.commands[cmdname] - - # Remove any command hooks set by the plugin. - for hookname, hookfuncs in world.hooks.copy().items(): - for hookfunc in hookfuncs: - if hookfunc.__module__ == name: - world.hooks[hookname].remove(hookfunc) - # If the hookfuncs list is empty, remove it. - if not hookfuncs: - del world.hooks[hookname] - - # Remove whois handlers too. - for f in world.whois_handlers: - if f.__module__ == name: - world.whois_handlers.remove(f) - - # Call the die() function in the plugin, if present. - if hasattr(pl, 'die'): - try: - pl.die(irc) - except: # But don't allow it to crash the server. - log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl) - - # Delete it from memory (hopefully). - del world.plugins[name] - if name in sys.modules: - del sys.modules[name] - if name in globals(): - del globals()[name] - - # Garbage collect. - gc.collect() - - irc.msg(irc.called_by, "Unloaded plugin %r." % name) - return True # We succeeded, make it clear (this status is used by reload() below) - else: - irc.msg(irc.called_by, "Unknown plugin %r." % name) -utils.add_cmd(unload) - -@utils.add_cmd -def reload(irc, source, args): - """. - - Loads a plugin from the plugin folder.""" - try: - name = args[0] - except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") - return - if unload(irc, source, args): - load(irc, source, args) - @utils.add_cmd def rehash(irc, source, args): """takes no arguments. From e942b411f110806f1f0a35718def1350d3b811d4 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 18:29:01 -0700 Subject: [PATCH 05/17] classes: introduce irc.reply() --- classes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/classes.py b/classes.py index c6f66d3..5c1ad4b 100644 --- a/classes.py +++ b/classes.py @@ -206,6 +206,10 @@ class Irc(): cmd = 'PYLINK_SELF_PRIVMSG' self.callHooks([source, cmd, {'target': target, 'text': text}]) + def reply(self, text, notice=False, source=None): + """Replies to the last caller in context.""" + self.msg(self.called_by, text, notice=notice, source=source) + def _disconnect(self): log.debug('(%s) Canceling pingTimer at %s due to _disconnect() call', self.name, time.time()) self.connected.clear() From 17a2dcd21ff80fe69b9eeb5d53cfc1fcf406f155 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 18:29:10 -0700 Subject: [PATCH 06/17] plugins: use irc.reply(...) instead of irc.msg(irc.called_by, ...) whereever possible --- coreplugin.py | 16 +++++----- plugins/bots.py | 46 ++++++++++++++-------------- plugins/commands.py | 32 ++++++++++---------- plugins/exec.py | 6 ++-- plugins/networks.py | 22 +++++++------- plugins/relay.py | 74 ++++++++++++++++++++++----------------------- 6 files changed, 98 insertions(+), 98 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index aa2617c..0ddd8d9 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -120,7 +120,7 @@ def identify(irc, source, args): Logs in to PyLink using the configured administrator account.""" if utils.isChannel(irc.called_by): - irc.msg(irc.called_by, 'Error: This command must be sent in private. ' + irc.reply('Error: This command must be sent in private. ' '(Would you really type a password inside a channel?)') return try: @@ -163,10 +163,10 @@ def load(irc, source, args): try: name = args[0] except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + irc.reply("Error: Not enough arguments. Needs 1: plugin name.") return if name in world.plugins: - irc.msg(irc.called_by, "Error: %r is already loaded." % name) + irc.reply("Error: %r is already loaded." % name) return try: world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder) @@ -180,7 +180,7 @@ def load(irc, source, args): if hasattr(pl, 'main'): log.debug('Calling main() function of plugin %r', pl) pl.main(irc) - irc.msg(irc.called_by, "Loaded plugin %r." % name) + irc.reply("Loaded plugin %r." % name) utils.add_cmd(load) def unload(irc, source, args): @@ -191,7 +191,7 @@ def unload(irc, source, args): try: name = args[0] except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + irc.reply("Error: Not enough arguments. Needs 1: plugin name.") return if name in world.plugins: pl = world.plugins[name] @@ -240,10 +240,10 @@ def unload(irc, source, args): # Garbage collect. gc.collect() - irc.msg(irc.called_by, "Unloaded plugin %r." % name) + irc.reply("Unloaded plugin %r." % name) return True # We succeeded, make it clear (this status is used by reload() below) else: - irc.msg(irc.called_by, "Unknown plugin %r." % name) + irc.reply("Unknown plugin %r." % name) utils.add_cmd(unload) @utils.add_cmd @@ -254,7 +254,7 @@ def reload(irc, source, args): try: name = args[0] except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + irc.reply("Error: Not enough arguments. Needs 1: plugin name.") return if unload(irc, source, args): load(irc, source, args) diff --git a/plugins/bots.py b/plugins/bots.py index c261835..4c07d3b 100644 --- a/plugins/bots.py +++ b/plugins/bots.py @@ -20,7 +20,7 @@ def spawnclient(irc, source, args): try: nick, ident, host = args[:3] except ValueError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 3: nick, user, host.") + irc.reply("Error: Not enough arguments. Needs 3: nick, user, host.") return irc.proto.spawnClient(nick, ident, host, manipulatable=True) @@ -33,15 +33,15 @@ def quit(irc, source, args): try: nick = args[0] except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: nick, reason (optional).") + irc.reply("Error: Not enough arguments. Needs 1-2: nick, reason (optional).") return if irc.pseudoclient.uid == utils.nickToUid(irc, nick): - irc.msg(irc.called_by, "Error: Cannot quit the main PyLink PseudoClient!") + irc.reply("Error: Cannot quit the main PyLink PseudoClient!") return u = utils.nickToUid(irc, nick) quitmsg = ' '.join(args[1:]) or 'Client Quit' if not utils.isManipulatableClient(irc, u): - irc.msg(irc.called_by, "Error: Cannot force quit a protected PyLink services client.") + irc.reply("Error: Cannot force quit a protected PyLink services client.") return irc.proto.quitClient(u, quitmsg) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}]) @@ -57,15 +57,15 @@ def joinclient(irc, source, args): if not clist: raise IndexError except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") + irc.reply("Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") return u = utils.nickToUid(irc, nick) if not utils.isManipulatableClient(irc, u): - irc.msg(irc.called_by, "Error: Cannot force join a protected PyLink services client.") + irc.reply("Error: Cannot force join a protected PyLink services client.") return for channel in clist: if not utils.isChannel(channel): - irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel) + irc.reply("Error: Invalid channel name %r." % channel) return irc.proto.joinClient(u, channel) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u], @@ -83,16 +83,16 @@ def nick(irc, source, args): nick = args[0] newnick = args[1] except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, newnick.") + irc.reply("Error: Not enough arguments. Needs 2: nick, newnick.") return u = utils.nickToUid(irc, nick) if newnick in ('0', u): newnick = u elif not utils.isNick(newnick): - irc.msg(irc.called_by, 'Error: Invalid nickname %r.' % newnick) + irc.reply('Error: Invalid nickname %r.' % newnick) return elif not utils.isManipulatableClient(irc, u): - irc.msg(irc.called_by, "Error: Cannot force nick changes for a protected PyLink services client.") + irc.reply("Error: Cannot force nick changes for a protected PyLink services client.") return irc.proto.nickClient(u, newnick) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}]) @@ -108,15 +108,15 @@ def part(irc, source, args): clist = args[1].split(',') reason = ' '.join(args[2:]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") + irc.reply("Error: Not enough arguments. Needs 2: nick, comma separated list of channels.") return u = utils.nickToUid(irc, nick) if not utils.isManipulatableClient(irc, u): - irc.msg(irc.called_by, "Error: Cannot force part a protected PyLink services client.") + irc.reply("Error: Cannot force part a protected PyLink services client.") return for channel in clist: if not utils.isChannel(channel): - irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel) + irc.reply("Error: Invalid channel name %r." % channel) return irc.proto.partClient(u, channel, reason) irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}]) @@ -133,12 +133,12 @@ def kick(irc, source, args): target = args[2] reason = ' '.join(args[3:]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).") + irc.reply("Error: Not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).") return u = utils.nickToUid(irc, nick) or nick targetu = utils.nickToUid(irc, target) if not utils.isChannel(channel): - irc.msg(irc.called_by, "Error: Invalid channel name %r." % channel) + irc.reply("Error: Invalid channel name %r." % channel) return if utils.isInternalServer(irc, u): irc.proto.kickServer(u, channel, targetu, reason) @@ -155,20 +155,20 @@ def mode(irc, source, args): try: modesource, target, modes = args[0], args[1], args[2:] except IndexError: - irc.msg(irc.called_by, 'Error: Not enough arguments. Needs 3: source nick, target, modes to set.') + irc.reply('Error: Not enough arguments. Needs 3: source nick, target, modes to set.') return target = utils.nickToUid(irc, target) or target extclient = target in irc.users and not utils.isInternalClient(irc, target) parsedmodes = utils.parseModes(irc, target, modes) ischannel = target in irc.channels if not (target in irc.users or ischannel): - irc.msg(irc.called_by, "Error: Invalid channel or nick %r." % target) + irc.reply("Error: Invalid channel or nick %r." % target) return elif not parsedmodes: - irc.msg(irc.called_by, "Error: No valid modes were given.") + irc.reply("Error: No valid modes were given.") return elif not (ischannel or utils.isManipulatableClient(irc, target)): - irc.msg(irc.called_by, "Error: Can only set modes on channels or non-protected PyLink clients.") + irc.reply("Error: Can only set modes on channels or non-protected PyLink clients.") return if utils.isInternalServer(irc, modesource): # Setting modes from a server. @@ -189,21 +189,21 @@ def msg(irc, source, args): try: msgsource, target, text = args[0], args[1], ' '.join(args[2:]) except IndexError: - irc.msg(irc.called_by, 'Error: Not enough arguments. Needs 3: source nick, target, text.') + irc.reply('Error: Not enough arguments. Needs 3: source nick, target, text.') return sourceuid = utils.nickToUid(irc, msgsource) if not sourceuid: - irc.msg(irc.called_by, 'Error: Unknown user %r.' % msgsource) + irc.reply('Error: Unknown user %r.' % msgsource) return if not utils.isChannel(target): real_target = utils.nickToUid(irc, target) if real_target is None: - irc.msg(irc.called_by, 'Error: Unknown user %r.' % target) + irc.reply('Error: Unknown user %r.' % target) return else: real_target = target if not text: - irc.msg(irc.called_by, 'Error: No text given.') + irc.reply('Error: No text given.') return irc.proto.messageClient(sourceuid, real_target, text) irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}]) diff --git a/plugins/commands.py b/plugins/commands.py index 9b3fab9..8ccf14b 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -17,10 +17,10 @@ def status(irc, source, args): Returns your current PyLink login status.""" identified = irc.users[source].identified if identified: - irc.msg(irc.called_by, 'You are identified as \x02%s\x02.' % identified) + irc.reply('You are identified as \x02%s\x02.' % identified) else: - irc.msg(irc.called_by, 'You are not identified as anyone.') - irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source))) + irc.reply('You are not identified as anyone.') + irc.reply('Operator access: \x02%s\x02' % bool(utils.isOper(irc, source))) def listcommands(irc, source, args): """takes no arguments. @@ -32,8 +32,8 @@ def listcommands(irc, source, args): nfuncs = len(world.commands[cmd]) if nfuncs > 1: cmds[idx] = '%s(x%s)' % (cmd, nfuncs) - irc.msg(irc.called_by, 'Available commands include: %s' % ', '.join(cmds)) - irc.msg(irc.called_by, 'To see help on a specific command, type \x02help \x02.') + irc.reply('Available commands include: %s' % ', '.join(cmds)) + irc.reply('To see help on a specific command, type \x02help \x02.') utils.add_cmd(listcommands, 'list') @utils.add_cmd @@ -52,7 +52,7 @@ def help(irc, source, args): else: funcs = world.commands[command] if len(funcs) > 1: - irc.msg(irc.called_by, 'The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s' + irc.reply('The following \x02%s\x02 plugins bind to the \x02%s\x02 command: %s' % (len(funcs), command, ', '.join([func.__module__ for func in funcs]))) for func in funcs: doc = func.__doc__ @@ -63,7 +63,7 @@ def help(irc, source, args): # arguments the command takes. lines[0] = '\x02%s %s\x02 (plugin: %r)' % (command, lines[0], mod) for line in lines: - irc.msg(irc.called_by, line.strip()) + irc.reply(line.strip()) else: irc.msg(source, "Error: Command %r (from plugin %r) " "doesn't offer any help." % (command, mod)) @@ -77,14 +77,14 @@ def showuser(irc, source, args): try: target = args[0] except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: nick.") + irc.reply("Error: Not enough arguments. Needs 1: nick.") return u = utils.nickToUid(irc, target) or target # Only show private info if the person is calling 'showuser' on themselves, # or is an oper. verbose = utils.isOper(irc, source) or u == source if u not in irc.users: - irc.msg(irc.called_by, 'Error: Unknown user %r.' % target) + irc.reply('Error: Unknown user %r.' % target) return f = lambda s: irc.msg(source, s) @@ -112,10 +112,10 @@ def showchan(irc, source, args): try: channel = utils.toLower(irc, args[0]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.") + irc.reply("Error: Not enough arguments. Needs 1: channel.") return if channel not in irc.channels: - irc.msg(irc.called_by, 'Error: Unknown channel %r.' % channel) + irc.reply('Error: Unknown channel %r.' % channel) return f = lambda s: irc.msg(source, s) @@ -155,15 +155,15 @@ def version(irc, source, args): """takes no arguments. Returns the version of the currently running PyLink instance.""" - irc.msg(irc.called_by, "PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version) - irc.msg(irc.called_by, "The source of this program is available at \x02%s\x02." % world.source) + irc.reply("PyLink version \x02%s\x02, released under the Mozilla Public License version 2.0." % world.version) + irc.reply("The source of this program is available at \x02%s\x02." % world.source) @utils.add_cmd def echo(irc, source, args): """ Echoes the text given.""" - irc.msg(irc.called_by, ' '.join(args)) + irc.reply(' '.join(args)) @utils.add_cmd def rehash(irc, source, args): @@ -178,7 +178,7 @@ def rehash(irc, source, args): new_conf = conf.validateConf(conf.loadConf(fname)) except Exception as e: # Something went wrong, abort. log.exception("Error REHASH'ing config: ") - irc.msg(irc.called_by, "Error loading configuration file: %s: %s", type(e).__name__, e) + irc.reply("Error loading configuration file: %s: %s", type(e).__name__, e) return conf.conf = new_conf for network, ircobj in world.networkobjects.copy().items(): @@ -198,4 +198,4 @@ def rehash(irc, source, args): if network not in world.networkobjects: proto = utils.getProtoModule(sdata['protocol']) world.networkobjects[network] = classes.Irc(network, proto, new_conf) - irc.msg(irc.called_by, "Done.") + irc.reply("Done.") diff --git a/plugins/exec.py b/plugins/exec.py index 6722ebe..3748d92 100755 --- a/plugins/exec.py +++ b/plugins/exec.py @@ -17,7 +17,7 @@ def _exec(irc, source, args): utils.checkAuthenticated(irc, source, allowOper=False) args = ' '.join(args) if not args.strip(): - irc.msg(irc.called_by, 'No code entered!') + irc.reply('No code entered!') return log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source)) exec(args, globals(), locals()) @@ -31,8 +31,8 @@ def _eval(irc, source, args): utils.checkAuthenticated(irc, source, allowOper=False) args = ' '.join(args) if not args.strip(): - irc.msg(irc.called_by, 'No code entered!') + irc.reply('No code entered!') return log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source)) - irc.msg(irc.called_by, eval(args)) + irc.reply(eval(args)) utils.add_cmd(_eval, 'eval') diff --git a/plugins/networks.py b/plugins/networks.py index 69502ad..a27be2f 100644 --- a/plugins/networks.py +++ b/plugins/networks.py @@ -22,12 +22,12 @@ def disconnect(irc, source, args): netname = args[0] network = world.networkobjects[netname] except IndexError: # No argument given. - irc.msg(irc.called_by, 'Error: Not enough arguments (needs 1: network name (case sensitive)).') + irc.reply('Error: Not enough arguments (needs 1: network name (case sensitive)).') return except KeyError: # Unknown network. - irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname) + irc.reply('Error: No such network "%s" (case sensitive).' % netname) return - irc.msg(irc.called_by, "Done.") + irc.reply("Done.") # Abort the connection! Simple as that. network.aborted.set() @@ -41,18 +41,18 @@ def connect(irc, source, args): netname = args[0] network = world.networkobjects[netname] except IndexError: # No argument given. - irc.msg(irc.called_by, 'Error: Not enough arguments (needs 1: network name (case sensitive)).') + irc.reply('Error: Not enough arguments (needs 1: network name (case sensitive)).') return except KeyError: # Unknown network. - irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname) + irc.reply('Error: No such network "%s" (case sensitive).' % netname) return if network.connection_thread.is_alive(): - irc.msg(irc.called_by, 'Error: Network "%s" seems to be already connected.' % netname) + irc.reply('Error: Network "%s" seems to be already connected.' % netname) else: # Reconnect the network! network.initVars() network.connection_thread = threading.Thread(target=network.connect) network.connection_thread.start() - irc.msg(irc.called_by, "Done.") + irc.reply("Done.") @utils.add_cmd def autoconnect(irc, source, args): @@ -66,13 +66,13 @@ def autoconnect(irc, source, args): seconds = float(args[1]) network = world.networkobjects[netname] except IndexError: # Arguments not given. - irc.msg(irc.called_by, 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).') + irc.reply('Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).') return except KeyError: # Unknown network. - irc.msg(irc.called_by, 'Error: No such network "%s" (case sensitive).' % netname) + irc.reply('Error: No such network "%s" (case sensitive).' % netname) return except ValueError: - irc.msg(irc.called_by, 'Error: Invalid argument "%s" for .' % seconds) + irc.reply('Error: Invalid argument "%s" for .' % seconds) return network.serverdata['autoconnect'] = seconds - irc.msg(irc.called_by, "Done.") + irc.reply("Done.") diff --git a/plugins/relay.py b/plugins/relay.py index affd071..18a7e32 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -1074,22 +1074,22 @@ def create(irc, source, args): try: channel = utils.toLower(irc, args[0]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.") + irc.reply("Error: Not enough arguments. Needs 1: channel.") return if not utils.isChannel(channel): - irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel) + irc.reply('Error: Invalid channel %r.' % channel) return if source not in irc.channels[channel].users: - irc.msg(irc.called_by, 'Error: You must be in %r to complete this operation.' % channel) + irc.reply('Error: You must be in %r to complete this operation.' % channel) return utils.checkAuthenticated(irc, source) localentry = getRelay((irc.name, channel)) if localentry: - irc.msg(irc.called_by, 'Error: Channel %r is already part of a relay.' % channel) + irc.reply('Error: Channel %r is already part of a relay.' % channel) return db[(irc.name, channel)] = {'claim': [irc.name], 'links': set(), 'blocked_nets': set()} initializeChannel(irc, channel) - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') @utils.add_cmd def destroy(irc, source, args): @@ -1099,10 +1099,10 @@ def destroy(irc, source, args): try: channel = utils.toLower(irc, args[0]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: channel.") + irc.reply("Error: Not enough arguments. Needs 1: channel.") return if not utils.isChannel(channel): - irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel) + irc.reply('Error: Invalid channel %r.' % channel) return utils.checkAuthenticated(irc, source) @@ -1112,9 +1112,9 @@ def destroy(irc, source, args): removeChannel(world.networkobjects.get(link[0]), link[1]) removeChannel(irc, channel) del db[entry] - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') else: - irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel) + irc.reply('Error: No such relay %r exists.' % channel) return @utils.add_cmd @@ -1127,7 +1127,7 @@ def link(irc, source, args): channel = utils.toLower(irc, args[1]) remotenet = args[0].lower() except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).") + irc.reply("Error: Not enough arguments. Needs 2-3: remote netname, channel, local channel name (optional).") return try: localchan = utils.toLower(irc, args[2]) @@ -1135,37 +1135,37 @@ def link(irc, source, args): localchan = channel for c in (channel, localchan): if not utils.isChannel(c): - irc.msg(irc.called_by, 'Error: Invalid channel %r.' % c) + irc.reply('Error: Invalid channel %r.' % c) return if source not in irc.channels[localchan].users: - irc.msg(irc.called_by, 'Error: You must be in %r to complete this operation.' % localchan) + irc.reply('Error: You must be in %r to complete this operation.' % localchan) return utils.checkAuthenticated(irc, source) if remotenet not in world.networkobjects: - irc.msg(irc.called_by, 'Error: No network named %r exists.' % remotenet) + irc.reply('Error: No network named %r exists.' % remotenet) return localentry = getRelay((irc.name, localchan)) if localentry: - irc.msg(irc.called_by, 'Error: Channel %r is already part of a relay.' % localchan) + irc.reply('Error: Channel %r is already part of a relay.' % localchan) return try: entry = db[(remotenet, channel)] except KeyError: - irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel) + irc.reply('Error: No such relay %r exists.' % channel) return else: if irc.name in entry['blocked_nets']: - irc.msg(irc.called_by, 'Error: Access denied (network is banned from linking to this channel).') + irc.reply('Error: Access denied (network is banned from linking to this channel).') return for link in entry['links']: if link[0] == irc.name: - irc.msg(irc.called_by, "Error: Remote channel '%s%s' is already" + irc.reply("Error: Remote channel '%s%s' is already" " linked here as %r." % (remotenet, channel, link[1])) return entry['links'].add((irc.name, localchan)) initializeChannel(irc, localchan) - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') @utils.add_cmd def delink(irc, source, args): @@ -1176,7 +1176,7 @@ def delink(irc, source, args): try: channel = utils.toLower(irc, args[0]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).") + irc.reply("Error: Not enough arguments. Needs 1-2: channel, remote netname (optional).") return try: remotenet = args[1].lower() @@ -1184,13 +1184,13 @@ def delink(irc, source, args): remotenet = None utils.checkAuthenticated(irc, source) if not utils.isChannel(channel): - irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel) + irc.reply('Error: Invalid channel %r.' % channel) return entry = getRelay((irc.name, channel)) if entry: if entry[0] == irc.name: # We own this channel. if not remotenet: - irc.msg(irc.called_by, "Error: You must select a network to " + irc.reply("Error: You must select a network to " "delink, or use the 'destroy' command to remove " "this relay entirely (it was created on the current " "network).") @@ -1203,9 +1203,9 @@ def delink(irc, source, args): else: removeChannel(irc, channel) db[entry]['links'].remove((irc.name, channel)) - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') else: - irc.msg(irc.called_by, 'Error: No such relay %r.' % channel) + irc.reply('Error: No such relay %r.' % channel) @utils.add_cmd def linked(irc, source, args): @@ -1247,37 +1247,37 @@ def linkacl(irc, source, args): cmd = args[0].lower() channel = utils.toLower(irc, args[1]) except IndexError: - irc.msg(irc.called_by, missingargs) + irc.reply(missingargs) return if not utils.isChannel(channel): - irc.msg(irc.called_by, 'Error: Invalid channel %r.' % channel) + irc.reply('Error: Invalid channel %r.' % channel) return relay = getRelay((irc.name, channel)) if not relay: - irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel) + irc.reply('Error: No such relay %r exists.' % channel) return if cmd == 'list': s = 'Blocked networks for \x02%s\x02: \x02%s\x02' % (channel, ', '.join(db[relay]['blocked_nets']) or '(empty)') - irc.msg(irc.called_by, s) + irc.reply(s) return try: remotenet = args[2] except IndexError: - irc.msg(irc.called_by, missingargs) + irc.reply(missingargs) return if cmd == 'deny': db[relay]['blocked_nets'].add(remotenet) - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') elif cmd == 'allow': try: db[relay]['blocked_nets'].remove(remotenet) except KeyError: - irc.msg(irc.called_by, 'Error: Network %r is not on the blacklist for %r.' % (remotenet, channel)) + irc.reply('Error: Network %r is not on the blacklist for %r.' % (remotenet, channel)) else: - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') else: - irc.msg(irc.called_by, 'Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd) + irc.reply('Error: Unknown subcommand %r: valid ones are ALLOW, DENY, and LIST.' % cmd) @utils.add_cmd def showuser(irc, source, args): @@ -1322,7 +1322,7 @@ def save(irc, source, args): Saves the relay database to disk.""" utils.checkAuthenticated(irc, source) exportDB() - irc.msg(irc.called_by, 'Done.') + irc.reply('Done.') @utils.add_cmd def claim(irc, source, args): @@ -1335,19 +1335,19 @@ def claim(irc, source, args): try: channel = utils.toLower(irc, args[0]) except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1-2: channel, list of networks (optional).") + irc.reply("Error: Not enough arguments. Needs 1-2: channel, list of networks (optional).") return # We override getRelay() here to limit the search to the current network. relay = (irc.name, channel) if relay not in db: - irc.msg(irc.called_by, 'Error: No such relay %r exists.' % channel) + irc.reply('Error: No such relay %r exists.' % channel) return claimed = db[relay]["claim"] try: nets = args[1].strip() except IndexError: # No networks given. - irc.msg(irc.called_by, 'Channel \x02%s\x02 is claimed by: %s' % + irc.reply('Channel \x02%s\x02 is claimed by: %s' % (channel, ', '.join(claimed) or '\x1D(none)\x1D')) else: if nets == '-' or not nets: @@ -1355,5 +1355,5 @@ def claim(irc, source, args): else: claimed = set(nets.split(',')) db[relay]["claim"] = claimed - irc.msg(irc.called_by, 'CLAIM for channel \x02%s\x02 set to: %s' % + irc.reply('CLAIM for channel \x02%s\x02 set to: %s' % (channel, ', '.join(claimed) or '\x1D(none)\x1D')) From cecb7f38007f3503006139514e2eaa15886fcc24 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 18:31:48 -0700 Subject: [PATCH 07/17] plugins: chmod -x --- plugins/exec.py | 0 plugins/fantasy.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 plugins/exec.py mode change 100755 => 100644 plugins/fantasy.py diff --git a/plugins/exec.py b/plugins/exec.py old mode 100755 new mode 100644 diff --git a/plugins/fantasy.py b/plugins/fantasy.py old mode 100755 new mode 100644 From b3ab604b8827a7ac9fe3b5a1021f3a4a5cbb0dfa Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 18:47:11 -0700 Subject: [PATCH 08/17] utils: return the bound function in add_cmd/add_hook TODO: allow utils.add_cmd/add_hook to be called as a decorator WITH a name argument --- utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils.py b/utils.py index d5125e7..2bcabb6 100644 --- a/utils.py +++ b/utils.py @@ -104,15 +104,18 @@ class TS6SIDGenerator(): return sid def add_cmd(func, name=None): + """Binds a command to the given command name.""" if name is None: name = func.__name__ name = name.lower() world.commands[name].append(func) + return func def add_hook(func, command): - """Add a hook for command .""" + """Binds a hook function to the given command.""" command = command.upper() world.hooks[command].append(func) + return func def toLower(irc, text): """Returns a lowercase representation of text based on the IRC object's From 2bf9568fbbd8ee595768d7cb0f631aa9378f2615 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 18:50:42 -0700 Subject: [PATCH 09/17] relay: Home network lost connection. => Relay network lost connection. --- plugins/relay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 18a7e32..7062407 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -1011,7 +1011,7 @@ def handle_disconnect(irc, numeric, command, args): del relayusers[k][irc.name] if k[0] == irc.name: try: - handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Home network lost connection.'}) + handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Relay network lost connection.'}) del relayusers[k] except KeyError: pass @@ -1024,7 +1024,7 @@ def handle_disconnect(irc, numeric, command, args): except KeyError: continue else: - ircobj.proto.squitServer(ircobj.sid, rsid, text='Home network lost connection.') + ircobj.proto.squitServer(ircobj.sid, rsid, text='Relay network lost connection.') del relayservers[name][irc.name] try: del relayservers[irc.name] From e4cfd1e1e6680e4eb0202b2371de3c93f8821255 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 19:07:17 -0700 Subject: [PATCH 10/17] Irc: log unrecognized commands to INFO too --- classes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classes.py b/classes.py index 5c1ad4b..9170365 100644 --- a/classes.py +++ b/classes.py @@ -178,11 +178,13 @@ class Irc(): return def callCommand(self, source, text): + """Calls a PyLink bot command.""" cmd_args = text.strip().split(' ') cmd = cmd_args[0].lower() cmd_args = cmd_args[1:] if cmd not in world.commands: self.msg(self.called_by or source, 'Error: Unknown command %r.' % cmd) + log.info('(%s) Received unknown command %r from %s', self.name, cmd, utils.getHostmask(self, source)) return log.info('(%s) Calling command %r for %s', self.name, cmd, utils.getHostmask(self, source)) for func in world.commands[cmd]: From 5ff82274e4df2741943b0a243538a0b8c44b32ad Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 19:07:39 -0700 Subject: [PATCH 11/17] coreplugin: normalize logging punctuation for logins --- coreplugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index 0ddd8d9..1e94c5d 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -133,12 +133,12 @@ def identify(irc, source, args): realuser = irc.conf['login']['user'] irc.users[source].identified = realuser irc.msg(source, 'Successfully logged in as %s.' % realuser) - log.info("(%s) Successful login to %r by %s.", + log.info("(%s) Successful login to %r by %s", irc.name, username, utils.getHostmask(irc, source)) else: irc.msg(source, 'Error: Incorrect credentials.') u = irc.users[source] - log.warning("(%s) Failed login to %r from %s.", + log.warning("(%s) Failed login to %r from %s", irc.name, username, utils.getHostmask(irc, source)) @utils.add_cmd From 5bccfcd17045fddc177b4f86745366803f0b0057 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 23 Oct 2015 19:08:03 -0700 Subject: [PATCH 12/17] coreplugin: log successful operups to INFO TODO: Investigate why this doesn't work on charybdis/TS6... --- coreplugin.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/coreplugin.py b/coreplugin.py index 1e94c5d..d167603 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -111,6 +111,11 @@ def handle_mode(irc, source, command, args): irc.proto.modeServer(irc.sid, target, {('+o', None)}) utils.add_hook(handle_mode, 'MODE') +def handle_operup(irc, source, command, args): + """Logs successful oper-ups on networks.""" + log.info("(%s) Successful oper-up (opertype %r) from %s", irc.name, args.get('text'), utils.getHostmask(irc, source)) +utils.add_hook(handle_operup, 'PYLINK_CLIENT_OPERED') + # Essential, core commands go here so that the "commands" plugin with less-important, # but still generic functions can be reloaded. From 9a1eab3b82c23b5f90fc3bdeb19fe47aa39d889c Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 25 Oct 2015 10:18:51 -0700 Subject: [PATCH 13/17] commands: add command to set log level (Closes #124) --- plugins/commands.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/commands.py b/plugins/commands.py index 8ccf14b..0da896c 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -199,3 +199,24 @@ def rehash(irc, source, args): proto = utils.getProtoModule(sdata['protocol']) world.networkobjects[network] = classes.Irc(network, proto, new_conf) irc.reply("Done.") + +loglevels = {'DEBUG': 10, 'INFO': 20, 'WARNING': 30, 'ERROR': 40, 'CRITICAL': 50} +@utils.add_cmd +def loglevel(irc, source, args): + """ + + Sets the log level to the given . must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL. + If no log level is given, shows the current one.""" + utils.checkAuthenticated(irc, source, allowOper=False) + try: + level = args[0].upper() + try: + loglevel = loglevels[level] + except KeyError: + irc.reply('Error: Unknown log level "%s".' % level) + return + else: + log.setLevel(loglevel) + irc.reply("Done.") + except IndexError: + irc.reply(log.getEffectiveLevel()) From 59af8e196cdf4f06b42fa2ba43c1c1ed426e1360 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 25 Oct 2015 10:27:06 -0700 Subject: [PATCH 14/17] ts6: fix call to operup hook when handling EUID --- protocols/ts6.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/protocols/ts6.py b/protocols/ts6.py index b0fc016..40fe271 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -578,9 +578,9 @@ class TS6Protocol(TS6BaseProtocol): log.debug('Applying modes %s for %s', parsedmodes, uid) utils.applyModes(self.irc, uid, parsedmodes) self.irc.servers[numeric].users.add(uid) - # Call the OPERED UP hook if +o is in the mode list. - if ('o', None) in parsedmodes: - otype = 'Server_Administrator' if ('a', None) in parsedmodes else 'IRC_Operator' + # Call the OPERED UP hook if +o is being added to the mode list. + if ('+o', None) in parsedmodes: + otype = 'Server_Administrator' if ('+a', None) in parsedmodes else 'IRC_Operator' self.irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}]) return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} From 7b444a72d8c1b9cb716275c619d8f490c88f9cd7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 25 Oct 2015 10:39:47 -0700 Subject: [PATCH 15/17] coreplugin: log plugin loading/unloading to INFO --- coreplugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coreplugin.py b/coreplugin.py index d167603..86d0168 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -173,6 +173,7 @@ def load(irc, source, args): if name in world.plugins: irc.reply("Error: %r is already loaded." % name) return + log.info('(%s) Loading plugin %r for %s', irc.name, name, utils.getHostmask(irc, source)) try: world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder) except ImportError as e: @@ -199,6 +200,7 @@ def unload(irc, source, args): irc.reply("Error: Not enough arguments. Needs 1: plugin name.") return if name in world.plugins: + log.info('(%s) Unloading plugin %r for %s', irc.name, name, utils.getHostmask(irc, source)) pl = world.plugins[name] log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl)) # Remove any command functions set by the plugin. From 86d3cd3295c33c0a8e1da807d992e2611f018e84 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sun, 25 Oct 2015 10:39:56 -0700 Subject: [PATCH 16/17] relay: only join the main pseudoclient once per channel --- plugins/relay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 7062407..c7b929e 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -363,7 +363,8 @@ def initializeChannel(irc, channel): # Send our users and channel modes to the other nets log.debug('(%s) initializeChannel: joining our (%s) users: %s', irc.name, remotenet, irc.channels[channel].users) relayJoins(irc, channel, irc.channels[channel].users, irc.channels[channel].ts) - irc.proto.joinClient(irc.pseudoclient.uid, channel) + if irc.pseudoclient.uid not in irc.channels[channel].users: + irc.proto.joinClient(irc.pseudoclient.uid, channel) def removeChannel(irc, channel): """Destroys a relay channel by parting all of its users.""" From c5b3441f6a27f11089a9c765d4b88a03061d5dff Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 31 Oct 2015 19:15:03 -0700 Subject: [PATCH 17/17] inspircd: alias SVSTOPIC to FTOPIC (reported by @siniStar7boy) SVSTOPIC is used by services to set topics when m_topiclock (server side topic locking) is enabled on the IRCd. Unfortunately, usage of this isn't documented anywehere...... Basically, this makes PyLink treat it as a regular server topic change (FTOPIC), since the arguments are the same anyways. --- protocols/inspircd.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 3953a8b..5a377d2 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -23,7 +23,7 @@ class InspIRCdProtocol(TS6BaseProtocol): # are called with the right hooks. self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE', 'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST', - 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'} + 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC'} self.sidgen = utils.TS6SIDGenerator(self.irc) self.uidgen = {} @@ -514,6 +514,9 @@ class InspIRCdProtocol(TS6BaseProtocol): self.irc.channels[channel].topicset = True return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic} + # SVSTOPIC is used by InspIRCd module m_topiclock - its arguments are the same as FTOPIC + handle_svstopic = handle_ftopic + def handle_invite(self, numeric, command, args): """Handles incoming INVITEs.""" # <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0