def msg(irc, source, args): """<source> <target> <text> Admin-only. Sends message <text> from <source>, where <source> is the nick of a PyLink client.""" utils.checkAuthenticated(irc, source, allowOper=False) try: msgsource, target, text = args[0], args[1], ' '.join(args[2:]) except IndexError: irc.reply( 'Error: Not enough arguments. Needs 3: source nick, target, text.') return sourceuid = utils.nickToUid(irc, msgsource) if not sourceuid: 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.reply('Error: Unknown user %r.' % target) return else: real_target = target if not text: 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' } ])
def part(irc, source, args): """<target> <channel1>,[<channel2>],... [<reason>] Admin-only. Parts <target>, the nick of a PyLink client, from a comma-separated list of channels.""" utils.checkAuthenticated(irc, source, allowOper=False) try: nick = args[0] clist = args[1].split(',') reason = ' '.join(args[2:]) except IndexError: 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.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.partClient(u, channel, reason) irc.callHooks([ u, 'PYLINK_BOTSPLUGIN_PART', { 'channels': clist, 'text': reason, 'parse_as': 'PART' } ])
def quit(irc, source, args): """<target> [<reason>] Admin-only. Quits the PyLink client with nick <target>, if one exists.""" utils.checkAuthenticated(irc, source, allowOper=False) try: nick = args[0] except IndexError: irc.reply( "Error: Not enough arguments. Needs 1-2: nick, reason (optional).") return if irc.pseudoclient.uid == utils.nickToUid(irc, nick): 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.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' }])
def load(irc, source, args): """<plugin name>. Loads a plugin from the plugin folder.""" utils.checkAuthenticated(irc, source, allowOper=False) try: name = args[0] except IndexError: irc.reply("Error: Not enough arguments. Needs 1: plugin name.") return 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: 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.reply("Loaded plugin %r." % name)
def joinclient(irc, source, args): """<target> <channel1>,[<channel2>], etc. Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels.""" utils.checkAuthenticated(irc, source, allowOper=False) try: nick = args[0] clist = args[1].split(',') if not clist: raise IndexError except IndexError: 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.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.joinClient(u, channel) irc.callHooks([ u, 'PYLINK_BOTSPLUGIN_JOIN', { 'channel': channel, 'users': [u], 'modes': irc.channels[channel].modes, 'parse_as': 'JOIN' } ])
def kick(irc, source, args): """<source> <channel> <user> [<reason>] Admin-only. Kicks <user> from <channel> via <source>, where <source> is the nick of a PyLink client.""" utils.checkAuthenticated(irc, source, allowOper=False) try: nick = args[0] channel = args[1] target = args[2] reason = ' '.join(args[3:]) except IndexError: 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.reply("Error: Invalid channel name %r." % channel) return if utils.isInternalServer(irc, u): irc.proto.kickServer(u, channel, targetu, reason) else: irc.proto.kickClient(u, channel, targetu, reason) irc.callHooks([ u, 'PYLINK_BOTSPLUGIN_KICK', { 'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK' } ])
def nick(irc, source, args): """<target> <newnick> Admin-only. Changes the nick of <target>, a PyLink client, to <newnick>.""" 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 u = utils.nickToUid(irc, nick) if newnick in ('0', u): 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.nickClient(u, newnick) irc.callHooks([ u, 'PYLINK_BOTSPLUGIN_NICK', { 'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK' } ])
def spawnclient(irc, source, args): """<nick> <ident> <host> Admin-only. Spawns the specified PseudoClient on the PyLink server. Note: this doesn't check the validity of any fields you give it!""" utils.checkAuthenticated(irc, source, allowOper=False) try: nick, ident, host = args[:3] except ValueError: irc.reply("Error: Not enough arguments. Needs 3: nick, user, host.") return irc.proto.spawnClient(nick, ident, host, manipulatable=True)
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 _eval(irc, source, args): """<Python expression> Admin-only. Evaluates the given Python expression and returns the result. \x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02""" utils.checkAuthenticated(irc, source, allowOper=False) args = ' '.join(args) if not args.strip(): irc.reply('No code entered!') return log.info('(%s) Evaluating %r for %s', irc.name, args, utils.getHostmask(irc, source)) irc.reply(eval(args))
def _exec(irc, source, args): """<code> Admin-only. Executes <code> in the current PyLink instance. \x02**WARNING: THIS CAN BE DANGEROUS IF USED IMPROPERLY!**\x02""" utils.checkAuthenticated(irc, source, allowOper=False) args = ' '.join(args) if not args.strip(): irc.reply('No code entered!') return log.info('(%s) Executing %r for %s', irc.name, args, utils.getHostmask(irc, source)) exec(args, globals(), locals())
def loglevel(irc, source, args): """<level> Sets the log level to the given <level>. <level> 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())
def disconnect(irc, source, args): """<network> Disconnects the network <network>. When all networks are disconnected, PyLink will automatically exit. Note: This does not affect the autoreconnect settings of any network, so the network will likely just reconnect unless autoconnect is disabled (see the 'autoconnect' command).""" utils.checkAuthenticated(irc, source, allowOper=False) try: netname = args[0] network = world.networkobjects[netname] except IndexError: # No argument given. irc.reply( 'Error: Not enough arguments (needs 1: network name (case sensitive)).' ) return except KeyError: # Unknown network. irc.reply('Error: No such network "%s" (case sensitive).' % netname) return irc.reply("Done.") # Abort the connection! Simple as that. network.aborted.set()
def rehash(irc, source, args): """takes no arguments. Reloads the configuration file for PyLink, (dis)connecting added/removed networks. Plugins must be manually reloaded.""" utils.checkAuthenticated(irc, source, allowOper=False) old_conf = conf.conf.copy() fname = conf.fname try: new_conf = conf.loadConf(fname, errors_fatal=False) except Exception as e: # Something went wrong, abort. log.exception("Error REHASH'ing config: ") irc.reply("Error loading configuration file: %s: %s" % (type(e).__name__, e)) return else: new_conf = conf.validateConf(new_conf) conf.conf = new_conf for network, ircobj in world.networkobjects.copy().items(): # Server was removed from the config file, disconnect them. log.debug('(%s) rehash: checking if %r is in new conf still.', irc.name, network) if network not in new_conf['servers']: log.debug( '(%s) rehash: removing connection to %r (removed from config).', irc.name, network) # Disable autoconnect first. ircobj.serverdata['autoconnect'] = -1 ircobj.aborted.set() del world.networkobjects[network] else: ircobj.conf = new_conf ircobj.serverdata = new_conf['servers'][network] ircobj.botdata = new_conf['bot'] for network, sdata in new_conf['servers'].items(): # New server was added. Connect them if not already connected. if network not in world.networkobjects: proto = utils.getProtoModule(sdata['protocol']) world.networkobjects[network] = classes.Irc( network, proto, new_conf) irc.reply("Done.")
def mode(irc, source, args): """<source> <target> <modes> Admin-only. Sets modes <modes> on <target> from <source>, where <source> is either the nick of a PyLink client, or the SID of a PyLink server. <target> can be either a nick or a channel.""" utils.checkAuthenticated(irc, source, allowOper=False) try: modesource, target, modes = args[0], args[1], args[2:] except IndexError: 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.reply("Error: Invalid channel or nick %r." % target) return elif not parsedmodes: irc.reply("Error: No valid modes were given.") return elif not (ischannel or utils.isManipulatableClient(irc, target)): 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. irc.proto.modeServer(modesource, target, parsedmodes) else: # Setting modes from a client. modesource = utils.nickToUid(irc, modesource) irc.proto.modeClient(modesource, target, parsedmodes) irc.callHooks([ modesource, 'PYLINK_BOTSPLUGIN_MODE', { 'target': target, 'modes': parsedmodes, 'parse_as': 'MODE' } ])
def autoconnect(irc, source, args): """<network> <seconds> Sets the autoconnect time for <network> to <seconds>. You can disable autoconnect for a network by setting <seconds> to a negative value.""" utils.checkAuthenticated(irc, source, allowOper=False) try: netname = args[0] seconds = float(args[1]) network = world.networkobjects[netname] except IndexError: # Arguments not given. irc.reply( 'Error: Not enough arguments (needs 2: network name (case sensitive), autoconnect time (in seconds)).' ) return except KeyError: # Unknown network. irc.reply('Error: No such network "%s" (case sensitive).' % netname) return except ValueError: irc.reply('Error: Invalid argument "%s" for <seconds>.' % seconds) return network.serverdata['autoconnect'] = seconds irc.reply("Done.")
def connect(irc, source, args): """<network> Initiates a connection to the network <network>.""" utils.checkAuthenticated(irc, source, allowOper=False) try: netname = args[0] network = world.networkobjects[netname] except IndexError: # No argument given. irc.reply( 'Error: Not enough arguments (needs 1: network name (case sensitive)).' ) return except KeyError: # Unknown network. irc.reply('Error: No such network "%s" (case sensitive).' % netname) return if network.connection_thread.is_alive(): 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.reply("Done.")
def unload(irc, source, args): """<plugin name>. Unloads a currently loaded plugin.""" utils.checkAuthenticated(irc, source, allowOper=False) try: name = args[0] except IndexError: 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. 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.reply("Unloaded plugin %r." % name) return True # We succeeded, make it clear (this status is used by reload() below) else: irc.reply("Unknown plugin %r." % name)