From d365f04199423a2b94233e1a71264caf2d50ce2a Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 7 Jul 2016 21:06:28 -0700 Subject: [PATCH] Automode plugin stub (#204) This supports adding/removing/listing entries and saving them to a DB, but no modes are set yet. --- __init__.py | 2 +- example-conf.yml | 4 + plugins/automode.py | 183 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 plugins/automode.py diff --git a/__init__.py b/__init__.py index 22c67d9..fa301bc 100644 --- a/__init__.py +++ b/__init__.py @@ -1,2 +1,2 @@ # Automatically generated by setup.py -__version__ = '0.9-dev1-24-g50d30d4' +__version__ = '0.9-dev1-27-g14b30b2' diff --git a/example-conf.yml b/example-conf.yml index 62f05e8..0022263 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -403,3 +403,7 @@ relay: games: # Sets the nick of the Games service, if you're using it. nick: Games + +automode: + # Sets the nick of the Automode service, if you're using it. + nick: ModeBot diff --git a/plugins/automode.py b/plugins/automode.py new file mode 100644 index 0000000..4f12975 --- /dev/null +++ b/plugins/automode.py @@ -0,0 +1,183 @@ +""" +automode.py - Provide simple channel ACL management by giving prefix modes to users matching +hostmasks or exttargets. +""" +import collections +import threading +import json + +from pylinkirc import utils +from pylinkirc.log import log + +mydesc = ("The \x02Automode\x02 plugin provides simple channel ACL management by giving prefix modes " + "to users matching hostmasks or exttargets.") + +# Register ourselves as a service. +modebot = utils.registerService("Automode", desc=mydesc) +reply = modebot.reply + +# Databasing variables. +dbname = utils.getDatabaseName('automode') +db = collections.defaultdict(dict) +exportdb_timer = None + +def loadDB(): + """Loads the Automode database, silently creating a new one if this fails.""" + global db + try: + with open(dbname, "r") as f: + db.update(json.load(f)) + except (ValueError, IOError, OSError): + log.info("Automode: failed to load links database %s; using the one in" + "memory.", dbname) + +def exportDB(): + """Exports the automode database.""" + + log.debug("Automode: exporting database to %s.", dbname) + with open(dbname, 'w') as f: + # Pretty print the JSON output for better readability. + json.dump(db, f, indent=4) + +def scheduleExport(starting=False): + """ + Schedules exporting of the Automode database in a repeated loop. + """ + global exportdb_timer + + if not starting: + # Export the database, unless this is being called the first + # thing after start (i.e. DB has just been loaded). + exportDB() + + # TODO: possibly make delay between exports configurable + exportdb_timer = threading.Timer(30, scheduleExport) + exportdb_timer.name = 'Automode exportDB Loop' + exportdb_timer.start() + +def main(irc=None): + """Main function, called during plugin loading at start.""" + + # Load the relay links database. + loadDB() + + # Schedule periodic exports of the automode database. + scheduleExport(starting=True) + +def die(sourceirc): + """Saves the Automode database and quit.""" + exportDB() + + # Kill the scheduling forexports. + global exportdb_timer + if exportdb_timer: + log.debug("Automode: cancelling exportDB timer thread %s due to die()", threading.get_ident()) + exportdb_timer.cancel() + +def setacc(irc, source, args): + """ + + Assigns the given prefix mode characters to the given mask for the channel given. Extended targets are supported for masks - use this to your advantage! + + Examples: + SET #channel *!*@localhost ohv + SET #channel $account v + SET #channel $oper:Network?Administrator qo + SET #staffchan $channel:#mainchan:op o + """ + irc.checkAuthenticated(source) + try: + channel, mask, modes = args + except ValueError: + reply(irc, "Error: Invalid arguments given. Needs 3: channel, mask, mode list.") + return + else: + if not utils.isChannel(channel): + reply(irc, "Error: Invalid channel name %s." % channel) + return + + # Store channels case insensitively + channel = irc.toLower(channel) + + # Database entries for any network+channel pair are automatically created using + # defaultdict. Note: string keys are used here instead of tuples so they can be + # exported easily as JSON. + dbentry = db[irc.name+channel] + + # Otherwise, update the modes as is. + dbentry[mask] = modes + reply(irc, "Done. \x02%s\x02 now has modes \x02%s\x02 in \x02%s\x02." % (mask, modes, channel)) + +modebot.add_cmd(setacc, 'setaccess') +modebot.add_cmd(setacc, 'set') +modebot.add_cmd(setacc, featured=True) + +def delacc(irc, source, args): + """ + + Removes the Automode entry for the given mask on the given channel, if one exists. + """ + irc.checkAuthenticated(source) + + try: + channel, mask = args + except ValueError: + reply(irc, "Error: Invalid arguments given. Needs 2: channel, mask") + return + + dbentry = db.get(irc.name+channel) + + if dbentry is None: + reply(irc, "Error: no Automode access entries exist for \x02%s\x02." % channel) + return + + if mask in dbentry: + del dbentry[mask] + reply(irc, "Done. Removed the Automode access entry for \x02%s\x02 in \x02%s\x02." % (mask, channel)) + else: + reply(irc, "Error: No Automode access entry for \x02%s\x02 exists in \x02%s\x02." % (mask, channel)) + + # Remove channels if no more entries are left. + if not dbentry: + log.debug("Automode: purging empty channel pair %s/%s", irc.name, channel) + del db[irc.name+channel] + + return +modebot.add_cmd(delacc, 'delaccess') +modebot.add_cmd(delacc, 'del') +modebot.add_cmd(delacc, featured=True) + +def listacc(irc, source, args): + """ + + Lists all Automode entries for the given channel.""" + irc.checkAuthenticated(source) + try: + channel = irc.toLower(args[0]) + except IndexError: + reply(irc, "Error: Invalid arguments given. Needs 1: channel.") + return + dbentry = db.get(irc.name+channel) + if not dbentry: + reply(irc, "Error: No Automode access entries exist for \x02%s\x02." % channel) + return + + else: + # Iterate over all entries and print them. Do this in private to prevent channel + # floods. + reply(irc, "Showing Automode entries for \x02%s\x02:" % channel, private=True) + for entrynum, entry in enumerate(dbentry.items(), start=1): + mask, modes = entry + reply(irc, "[%s] \x02%s\x02 has modes +\x02%s\x02" % (entrynum, mask, modes), private=True) + reply(irc, "End of Automode entries list.") +modebot.add_cmd(listacc, featured=True) +modebot.add_cmd(listacc, 'listaccess') + +def save(irc, source, args): + """takes no arguments. + + Saves the Automode database to disk.""" + irc.checkAuthenticated(source) + exportDB() + reply(irc, 'Done.') +modebot.add_cmd(save)