office: Integrate neomutt and taskwarrior

The beginning of what I hope can be a useful integration: send mails to
taskwarrior as tasks and open the corresponding mail from tasks in
taskwarrior.

To make a task out of an e-mail, in neomutt, simply press `t` when the
mail is selected or opened. It will create an automatic task in
taskwarrior with the description "Reply to [mail] by [sender]" and tag
it as mail.
If you press `T` instead, you can give the task your own description and
tags.

In taskwarrior, you can simply `t open <taskid>` on a task that came
from neomutt to show the message content on the command line (using
notmuch). This is still a bit rudimentary and I would like an improved
display, but it works for now.
This commit is contained in:
Marty Oehme 2022-12-08 21:31:15 +01:00
parent a89249badd
commit aefce1c498
Signed by: Marty
GPG key ID: 73BA40D5AFAF49C9
3 changed files with 107 additions and 0 deletions

View file

@ -59,3 +59,7 @@ macro index o "<sync-mailbox><shell-escape>export MBSYNC_PRE=true; sync-mail gma
# Saner copy/move dialogs # Saner copy/move dialogs
macro index C "<copy-message>?<toggle-mailboxes>" "copy a message to a mailbox" macro index C "<copy-message>?<toggle-mailboxes>" "copy a message to a mailbox"
macro index M "<save-message>?<toggle-mailboxes>" "move a message to a mailbox" macro index M "<save-message>?<toggle-mailboxes>" "move a message to a mailbox"
# Send mail to taskwarrior
macro index,pager T "<pipe-message>mutt2task -c -d -t<enter>" "add mail as task to taskwarrior with custom description and tags"
macro index,pager t "<pipe-message>mutt2task -c<enter>" "add mail as task to taskwarrior"

View file

@ -13,5 +13,8 @@ notes.command = "$EDITOR ${XDG_DATA_HOME:-~/.local/share}/task/notes/$UUID.md"
links.regex = "^https?://" links.regex = "^https?://"
links.command = "xdg-open $FILE" links.command = "xdg-open $FILE"
mail.regex = "^<.*@.*>$"
mail.command = "notmuch show mid:${FILE:1:-1}"
[CLI] [CLI]

100
office/.local/bin/mutt2task Executable file
View file

@ -0,0 +1,100 @@
#!/usr/bin/env python
"""
Creates a new task with corresponding description and tags.
Description and tags can be supplied via commandline through `-d` and `-t` options,
or interactively by using the options without supplying a string for them. E.g.:
`mutt2task` => task: Reply to <subject> from <sender>
`mutt2task -d "hello" -t one two` => task: hello +one +two
`mutt2task -d -t` => asks you interactively for description and tags
"""
import argparse
import email
import sys
import re
from tasklib import TaskWarrior, Task
def get_args():
parser = argparse.ArgumentParser(
description="Creates a task with given message and annotates the task with message id"
)
parser.add_argument(
"-d",
"--description",
nargs="?",
const=True,
default=None,
help="Supply a description for the task. Use the option as a flag to supply description interactively.",
)
parser.add_argument(
"-t",
"--tags",
nargs="*",
help="Supply any number of tags for the task. Use the option as a flag to supply tags interactively.",
)
parser.add_argument(
"-c",
"--clean",
action="store_true",
help="Remove subject annoyances like RE FWD on a best-effort basis.",
)
return parser.parse_args()
args = get_args()
def clean_subject(subject):
to_remove = ["fwd", "re", "fw", "aw", "wg", "sv", "vs", "ref", "tr"]
for abbrev in to_remove:
patt = re.compile(r"^{}(?: |:)\s*".format(abbrev), re.IGNORECASE)
subject = patt.sub("", subject)
return subject
def get_task_description(message):
subject = clean_subject(message["subject"]) if args.clean else message["subject"]
description = 'Reply to "{}" from {}'.format(
subject, message["from"].split(" <")[0]
)
# automatically set description if one provided
if args.description and isinstance(args.description, str):
description = args.description
# otherwise let user enter one if option has been passed
elif args.description:
inp = input("Enter task description or press enter for default: ")
description = inp if inp else description
return description
def get_task_tags():
tags = ["mail"]
if args.tags and len(args.tags) > 0:
tags = sum([tag.split(" ") for tag in args.tags], [])
elif args.tags is not None and len(args.tags) == 0:
inp = input("Enter task tags or press enter for default: ")
tags = inp.split(" ") if inp else tags
return tags
def main():
try:
message = email.message_from_file(sys.stdin)
if not message:
print("Something went wrong, cannot parse mail.")
sys.exit(1)
tw = TaskWarrior("~/.local/share/task", create=False)
sys.stdin = open("/dev/tty") # make user able to add input again
task = Task(tw, description=get_task_description(message), tags=get_task_tags())
task.save()
task.add_annotation(message['message-id'])
except Exception as e:
print(e)
sys.exit(2)
if __name__ == "__main__":
main()