From 47cdc830a00ffd2c33b373bf41888d91ac138a29 Mon Sep 17 00:00:00 2001 From: Geoffrey Frogeye Date: Tue, 14 Aug 2018 10:08:59 +0200 Subject: [PATCH] mel2 --- scripts/mel | 193 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 149 insertions(+), 44 deletions(-) diff --git a/scripts/mel b/scripts/mel index 4922bd1..7a4dae5 100755 --- a/scripts/mel +++ b/scripts/mel @@ -19,39 +19,25 @@ import argparse import configparser import base64 import shutil +import argparse +import xdg.BaseDirectory +import sys +import subprocess -colorama.init() -coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') -log = logging.getLogger() +ACCOUNTS = dict() +ALIASES = set() -log.debug("Loading config") +def generate_aliases(): + for name in config.sections(): + if not name.islower(): + continue + section = config[name] + ALIASES.add(section["from"]) + if "alternatives" in section: + for alt in section["alternatives"].split(";"): + ALIASES.add(alt) + ACCOUNTS[name] = section -# TODO XDG -configPath = os.path.join(os.path.expanduser('~'), '.config', 'mel', 'accounts.conf') - -config = configparser.ConfigParser() -config.read(configPath) - -# Reading config a bit -accounts = dict() -mails = set() -for name in config.sections(): - if not name.islower(): - continue - section = config[name] - mails.add(section["from"]) - if "alternatives" in section: - for alt in section["alternatives"].split(";"): - mails.add(alt) - accounts[name] = section - -log.debug("Loading database") - -dbPath = os.path.realpath(os.path.expanduser(config["GENERAL"]["storage"])) -db = notmuch.Database(mode=notmuch.Database.MODE.READ_WRITE, path=dbPath) -# TODO Open read-only when needed - -log.debug("Database loaded") def get_location(msg): path = msg.get_filename() @@ -61,6 +47,7 @@ def get_location(msg): path = path[len(base):] pathSplit = path.split('/') mailbox = pathSplit[1] + assert mailbox in ACCOUNTS state = pathSplit[-1] folder = tuple(pathSplit[2:-1]) assert state in {'cur', 'tmp', 'new'} @@ -193,25 +180,14 @@ def retag_msg(msg): tag_if('spam', slugFolder[0] == 'JUNK' or slugFolder[0] == 'SPAM') tag_if('deleted', slugFolder[0] == 'TRASH') tag_if('draft', slugFolder[0] == 'DRAFTS') - tag_if('sent', expeditor in mails) + tag_if('sent', expeditor in ALIASES) + # TODO remove unprocessed # Save msg.thaw() msg.tags_to_maildir_flags() -def applyMsgs(queryStr, function, *args, useProgressbar=False, **kwargs): - query = notmuch.Query(db, queryStr) - query.set_sort(notmuch.Query.SORT.OLDEST_FIRST) - msgs = query.search_messages() - if useProgressbar: - nbMsgs = query.count_messages() - iterator = progressbar.progressbar(msgs, max_value=nbMsgs) - else: - iterator = msgs - for msg in iterator: - function(msg, *args, **kwargs) - def extract_email(field): try: sta = field.index('<') @@ -220,8 +196,25 @@ def extract_email(field): except ValueError: return field +def applyMsgs(queryStr, action, *args, showProgress=False, **kwargs): + log.info("Querying {}".format(queryStr)) + query = notmuch.Query(db, queryStr) + query.set_sort(notmuch.Query.SORT.OLDEST_FIRST) + + elements = query.search_messages() + + if showProgress: + nbMsgs = query.count_messages() + iterator = progressbar.progressbar(elements, max_value=nbMsgs) + else: + iterator = elements + + log.info("Executing {}".format(action)) + for msg in iterator: + action(msg, *args, **kwargs) + # applyMsgs('*', print_msg) -applyMsgs('tag:inbox', print_msg) +# applyMsgs('tag:inbox', print_msg) # applyMsgs('tag:spam', print_msg) # applyMsgs('tag:unread', print_msg) # applyMsgs('tag:unprocessed', print_msg) @@ -230,3 +223,115 @@ applyMsgs('tag:inbox', print_msg) # applyMsgs('tag:unprocessed', retag_msg, useProgressbar=True) # applyMsgs('*', retag_msg, useProgressbar=True) +if __name__ == "__main__": + # Main arguments + parser = argparse.ArgumentParser(description="Meh mail client") + selectedVerbosityLevels = ["DEBUG", "INFO", "WARNING", "ERROR", "FATAL"] + parser.add_argument('-v', '--verbosity', choices=selectedVerbosityLevels, default='WARNING', help="Verbosity of log messages") + # parser.add_argument('-n', '--dry-run', action='store_true', help="Don't do anything") # DEBUG + defaultConfigFile = os.path.join(xdg.BaseDirectory.xdg_config_home, 'mel', 'accounts.conf') + parser.add_argument('-c', '--config', default=defaultConfigFile, help="Accounts config file") + + parser.set_defaults(dbmode=notmuch.Database.MODE.READ_ONLY) + parser.set_defaults(showProgress=False) + parser.set_defaults(useThreads=False) + parser.set_defaults(actionBefore=None) + parser.set_defaults(actionAfter=None) + parser.set_defaults(action=None) + + subparsers = parser.add_subparsers(help="Action to execute", required=True) + + + ## List messages + + + # inbox (default) + def func_inbox(args): + queryStr = 'tag:unread' if args.only_unread else 'tag:inbox' + applyMsgs(queryStr, print_msg) + + parserInbox = subparsers.add_parser("inbox", help="Show unread, unsorted and flagged messages") + parserInbox.add_argument('-u', '--only-unread', action='store_true', help="Show unread messages only") + # TODO Make this more relevant + parserInbox.set_defaults(func=func_inbox) + + + # list folder [--recurse] + ## List actions + # delete msg... + # spam msg... + # flag msg... + # ↑un* equivalents + # move dest msg... + ## Read message + # read msg [--html] [--plain] [--browser] + # attach msg [id] [--save] (list if no id, xdg-open else) + ## Redaction + # new account + # reply msg [--all] + ## Folder management + # mkdir folder + # rmdir folder (prevent if folder isn't empty (mail/subfolder)) + # (yeah that should do) + ## Meta + # setup (interactive thing maybe) + + + # fetch (mbsync, notmuch new, retag, notify; called by greater gods) + def func_fetch(args): + # Fetch mails + log.info("Fetching mails") + mbsyncConfigPath = os.path.expanduser("~/.mbsyncrc") # TODO Better + cmd = ["mbsync", "--config", mbsyncConfigPath, "--all"] + subprocess.run(cmd) + + # Index new mails + log.info("Indexing mails") + log.error("TODO Can't `notmuch new` when database is already open!") + notmuchConfigPath = os.path.expanduser("~/.notmuchrc") # TODO Better + cmd = ["notmuch", "--config", notmuchConfigPath, "new"] + log.debug(" ".join(cmd)) + subprocess.run(cmd) + + # Tag new mails + applyMsgs('tag:unprocessed', retag_msg, showProgress=True) + + # Notify + log.info("Notifying new mails") + # TODO Maybe before retag, notify unprocessed && unread + + parserFetch = subparsers.add_parser("fetch", help="Fetch mail, tag them, and run notifications") + parserFetch.set_defaults(dbmode=notmuch.Database.MODE.READ_WRITE) + parserFetch.set_defaults(func=func_fetch) + + + ## Debug + # process (all or unprocessed) + + args = parser.parse_args() + print(args) + + # Installing logs + colorama.init() + coloredlogs.install(level=args.verbosity, fmt='%(levelname)s %(message)s') + log = logging.getLogger() + + log.info("Loading config {}".format(args.config)) + if not os.path.isfile(args.config): + log.fatal("Config file not found: {}".format(args.config)) + sys.exit(1) + # TODO Create it, maybe? + config = configparser.ConfigParser() + config.read(args.config) + + generate_aliases() + + if args.dbmode is not None: + log.info("Loading database") + dbPath = os.path.realpath(os.path.expanduser(config["GENERAL"]["storage"])) + db = notmuch.Database(mode=args.dbmode, path=dbPath) + + if args.func: + log.info("Executing function {}".format(args.func)) + args.func(args) +