Last active
March 28, 2022 11:38
-
-
Save mskian/e7b4a6e2b068bd1e6605547f498e3ed6 to your computer and use it in GitHub Desktop.
Gotify Python CLI for Termux - Send Messages to the Gotify/server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Url of the Gotify server (including the protocol to use) | |
url: 'https://push.example.com' | |
# Default notification parameters | |
default: | |
token: 'XXXXXXXXXXXXXX' | |
title: 'Hello World' | |
message: 'Hello Push From Python CLI' | |
priority: 5 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import sys | |
import time | |
import os.path | |
from urllib.parse import urljoin | |
import argparse | |
import requests | |
import yaml | |
from halo import Halo | |
APP_NAME = 'gotify' | |
CONFIG_NAME = 'gfycli' | |
config = None | |
def main(): | |
# Parse and declare arguments | |
args = parseArgs() | |
# Check if url argument supplied (if so, don't load config) | |
url = parseURLAndConfig(args) | |
# Load app token (either from map, from argument, or default from config) | |
token = parseToken(args) | |
# Load title (either from argument, or default from config) | |
title = parseTitle(args) | |
# Load message (either from argument, from stdin, or default from config) | |
message = parseMessage(args) | |
# Load priority (either from argument, or default from config) | |
priority = parsePriority(args) | |
# Perform request | |
doRequest(url, token, title, message, priority) | |
def parseArgs(): | |
# Argument parser declaration | |
parser = argparse.ArgumentParser(description='A simple CLI client that pushes notifications to the Gotify REST-API. Can also read and use default values from config files.') | |
groupUrlOrConfig = parser.add_mutually_exclusive_group() | |
groupUrlOrConfig.add_argument('-u', '--url', help='url of Gotify server (overrides config)') | |
groupUrlOrConfig.add_argument('-c', '--config', help='file path of a yaml config file') | |
groupTokenOrKey = parser.add_mutually_exclusive_group() | |
groupTokenOrKey.add_argument('-a', '--app-token', help='gotify app token to which to send the notification') | |
parser.add_argument('-t', '--title', help='the title of the notification') | |
parser.add_argument('-m', '--message', help='the message of the notification (use \'-\' to read from stdin)') | |
parser.add_argument('-p', '--priority', type=int, help='the priority of the notification') | |
return parser.parse_args() | |
def parseURLAndConfig(args): | |
# Declare variable config as reference to global config variable in this function | |
global config | |
if args.url is not None: | |
# This check is necessary since at this point in time argparse does not | |
# yet support "necessarily inclusive" groups | |
# (https://bugs.python.org/issue11588) | |
if not (args.app_token is not None and args.title is not None and args.message is not None and args.priority is not None) or args.key is not None: | |
sys.exit('The following arguments are required when using url argument:\n-a/--app-token -t/--title -m/--message -p/--priority\n') | |
# Load url from argument | |
return args.url | |
# Load config | |
if args.config is not None: | |
# Load config from path provided as argument | |
config = loadConfig(args.config) | |
else: | |
# Load config file from predefined locations | |
config = loadDefaultConfig() | |
# Load url from config | |
return config['url'] | |
def parseToken(args): | |
if args.app_token is not None: | |
return args.app_token | |
return config['default']['token'] | |
def parseTitle(args): | |
if args.title is not None: | |
return args.title | |
return config['default']['title'] | |
def parseMessage(args): | |
if args.message is not None: | |
if args.message is '-': | |
return bytes.decode(sys.stdin.buffer.read()) | |
return args.message | |
return config['default']['message'] | |
def parsePriority(args): | |
if args.priority is not None: | |
return args.priority | |
return config['default']['priority'] | |
def loadConfig(configFile): | |
if os.path.isfile(configFile): | |
file = open(configFile, 'r') | |
yamlString = file.read() | |
file.close() | |
else: | |
sys.exit('Could not load config: ' + os.path) | |
return yaml.load(yamlString, Loader=yaml.SafeLoader) | |
def loadDefaultConfig(): | |
configFileLocations = getDefaultConfigFileLocations() | |
configLoaded = False | |
# Load config files in array order (multiple configs will override each other) | |
for configFile in configFileLocations: | |
if os.path.isfile(configFile): | |
file = open(configFile, 'r') | |
yamlString = file.read() | |
file.close() | |
configLoaded = True | |
# Exit with error if no config was loaded | |
if not configLoaded: | |
configFileLocations = '\n'.join(map(str, getDefaultConfigFileLocations())) | |
sys.exit('No config file found! Possible locations are:\n' + configFileLocations) | |
return yaml.load(yamlString, Loader=yaml.SafeLoader) | |
def getDefaultConfigFileLocations(): | |
HOME = os.path.expanduser('~') | |
configFileLocations = [ | |
'/etc/' + APP_NAME + '/' + CONFIG_NAME + '.yaml', | |
'/etc/' + APP_NAME + '/' + CONFIG_NAME + '.yml', | |
HOME + '/.' + APP_NAME + '/' + CONFIG_NAME + '.yaml', | |
HOME + '/.' + APP_NAME + '/' + CONFIG_NAME + '.yml', | |
HOME + '/.config/' + APP_NAME + '/' + CONFIG_NAME + '.yaml', | |
HOME + '/.config/' + APP_NAME + '/' + CONFIG_NAME + '.yml', | |
HOME + '/' + CONFIG_NAME + '.yaml', | |
HOME + '/' + CONFIG_NAME + '.yml', | |
'./' + CONFIG_NAME + '.yaml', | |
'./' + CONFIG_NAME + '.yml' | |
] | |
return configFileLocations | |
def doRequest(url, token, title, message, priority): | |
try: | |
spinner = Halo(text='Connected...', color='cyan') | |
spinner.start() | |
time.sleep(1) | |
spinner.text = 'Sending Message...' | |
requestURL = urljoin(url, '/message?token=' + token) | |
time.sleep(2) | |
resp = requests.post(requestURL, json={ | |
'title': title, | |
'message': message, | |
'priority': priority | |
}) | |
spinner.stop() | |
except requests.ConnectionError as e: | |
spinner.stop() | |
print('Oops - Enter a Valid URL') | |
except requests.exceptions.RequestException as e: | |
spinner.stop() | |
# Print exception if reqeuest fails | |
sys.exit('Could not connect to Gotify server:\n' + str(e)) | |
except (KeyboardInterrupt, SystemExit): | |
spinner.stop() | |
print("Ok ok, quitting") | |
sys.exit(1) | |
else: | |
# Print request result if server returns http error code | |
if resp.status_code is not requests.codes['ok']: | |
sys.exit(bytes.decode(resp.content)) | |
else: | |
print('Message Sent Successfully') | |
if __name__ == "__main__": | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
requests>=2.20.0 | |
PyYAML>=5.1 | |
halo>=0.0.23 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment