From 22b1ee96b39d985a8822d0c1bc38c99c81a966d7 Mon Sep 17 00:00:00 2001 From: Damien Dart Date: Sun, 13 Oct 2024 15:57:50 +0100 Subject: [PATCH] Add initial implementation of "po". Add initial implementation of "po", an application for sending notifications using Pushover. --- .editorconfig | 2 +- Taskfile.yml | 4 +- bin/po | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100755 bin/po diff --git a/.editorconfig b/.editorconfig index 7ff5369..2f025b9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[{git-hometime,install,markdown-tidy,rpncalc,timeshit,*.py}] +[{git-hometime,install,markdown-tidy,po,rpncalc,timeshit,*.py}] indent_size = 4 [toolbox.txt] diff --git a/Taskfile.yml b/Taskfile.yml index 1e58c93..8f9cf53 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -45,12 +45,12 @@ tasks: lint:python:black: cmds: - - '.venv/bin/black --check --diff install bin/git-hometime bin/markdown-tidy bin/rpncalc bin/timeshit' + - '.venv/bin/black --check --diff install bin/git-hometime bin/markdown-tidy bin/po bin/rpncalc bin/timeshit' desc: 'Lint Python scripts with Black' lint:python:flake8: cmds: - - '.venv/bin/flake8 install bin/git-hometime bin/markdown-tidy bin/rpncalc bin/timeshit' + - '.venv/bin/flake8 install bin/git-hometime bin/markdown-tidy bin/po bin/rpncalc bin/timeshit' desc: 'Lint Python scripts with Flake8' lint:python:mypy: diff --git a/bin/po b/bin/po new file mode 100755 index 0000000..b02387a --- /dev/null +++ b/bin/po @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +A simple notification sender, using Pushover. + +A configuration file at "$HOME/.config/toolbox/po.ini" is used to +provide Pushover API tokens: + + [pushover] + application_key=APPLICATION_KEY_GOES_HERE + user_key=USER_KEY_GOES_HERE +""" + +# This file was written by Damien Dart, . This is +# free and unencumbered software released into the public domain. For +# more information, please refer to the accompanying "UNLICENCE" file. + +import argparse +import configparser +import http.client +import os +import pathlib +import sys +import typing +import urllib.parse + + +CONFIG_DEFAULT = "{}/.config/toolbox/po.ini".format(pathlib.Path.home()) + + +def fatal_error(message: str) -> None: + print( + "{}: error: {}".format(os.path.basename(sys.argv[0]), message), + file=sys.stderr, + ) + + raise SystemExit(1) + + +def main(arguments: argparse.Namespace, config: typing.List) -> None: + connection = http.client.HTTPSConnection("api.pushover.net:443") + connection.request( + "POST", + "/1/messages.json", + urllib.parse.urlencode( + { + "message": " ".join(arguments.message), + "title": arguments.title, + "token": config["application_key"], + "user": config["user_key"], + }, + ), + {"Content-Type": "application/x-www-form-urlencoded"}, + ) + + response = connection.getresponse() + if response.status == 200: + return + else: + fatal_error('Pushover API responded with "{}"'.format(response.reason)) + + +def parse_arguments(arguments: typing.List[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "message", + help="notification body", + nargs="*", + type=str, + default=sys.stdin, + ) + parser.add_argument( + "--title", + help="notification title", + nargs="?", + type=str, + default="Wild notification appeared!", + ) + + return parser.parse_args(arguments) + + +def parse_config(config_file: str): + if not (os.path.isfile(config_file) and os.access(config_file, os.R_OK)): + fatal_error('unable to read config file "{}"'.format(config_file)) + + with open(config_file, "r") as f: + config_file = f.read() + + config = configparser.ConfigParser() + config.read_string(config_file) + + if not config.has_option("pushover", "application_key"): + fatal_error('missing "pushover.application_key" option in config file') + + if not config.has_option("pushover", "user_key"): + fatal_error('missing "pushover.user_key" option in config file') + + return { + "application_key": config.get("pushover", "application_key"), + "user_key": config.get("pushover", "user_key"), + } + + +if __name__ == "__main__": + arguments = parse_arguments(sys.argv[1:]) + config = parse_config(CONFIG_DEFAULT) + + main(arguments, config)