#!/usr/bin/env python3 """ simple client for que.run """ import argparse import configparser import http.client import os import subprocess import sys import time import urllib.parse import urllib.request as request MAX_TIMEOUT = 99999999 # basically never timeout def auth(args): "Returns the auth key for the given ns from ~/.config/que.conf" namespace = args.target.split("/")[0] if namespace == "pub": return None conf_file = os.path.expanduser("~/.config/que.conf") if not os.path.exists(conf_file): sys.exit("you need a ~/.config/que.conf") cfg = configparser.ConfigParser() cfg.read(conf_file) return cfg[namespace]["key"] def send(args): "Send a message to the que." key = auth(args) data = args.infile req = request.Request(f"{args.host}/{args.target}") req.add_header("User-AgenT", "Que/Client") if key: req.add_header("Authorization", key) if args.serve: while not time.sleep(1): request.urlopen(req, data=data, timeout=MAX_TIMEOUT) else: request.urlopen(req, data=data, timeout=MAX_TIMEOUT) def recv(args): "Receive a message from the que." def _recv(_req): msg = autodecode(_req.read()) print(msg) if args.then: subprocess.run( args.then.replace(r"\msg", msg).replace(r"que", args.target), shell=True, check=False, ) params = urllib.parse.urlencode({"poll": args.poll}) req = request.Request(f"{args.host}/{args.target}?{params}") req.add_header("User-Agent", "Que/Client") key = auth(args) if key: req.add_header("Authorization", key) with request.urlopen(req) as _req: if args.poll: while not time.sleep(1): _recv(_req) else: _recv(_req) def autodecode(bytestring): """Attempt to decode bytes `bs` into common codecs, preferably utf-8. If no decoding is available, just return the raw bytes. For all available codecs, see: """ codecs = ["utf-8", "ascii"] for codec in codecs: try: return bytestring.decode(codec) except UnicodeDecodeError: pass return bytestring def get_args(): "Command line parser" cli = argparse.ArgumentParser(description=__doc__) cli.add_argument( "--host", default="http://que.run", help="where que-server is running" ) cli.add_argument( "--poll", default=False, action="store_true", help="stream data from the que" ) cli.add_argument( "--then", help=" ".join( [ "when polling, run this shell command after each response,", "presumably for side effects," r"replacing '\que' with the target and '\msg' with the body of the response", ] ), ) cli.add_argument( "--serve", default=False, action="store_true", help=" ".join( [ "when posting to the que, do so continuously in a loop.", "this can be used for serving a webpage or other file continuously", ] ), ) cli.add_argument( "target", help="namespace and path of the que, like 'ns/path/subpath'" ) cli.add_argument( "infile", nargs="?", type=argparse.FileType("rb"), help="data to put on the que. Use '-' for stdin, otherwise should be a readable file", ) return cli.parse_args() if __name__ == "__main__": ARGV = get_args() try: if ARGV.infile: send(ARGV) else: recv(ARGV) except KeyboardInterrupt: sys.exit(0) except urllib.error.HTTPError as err: print(err) sys.exit(1) except http.client.RemoteDisconnected as err: print("disconnected... retrying in 5 seconds") time.sleep(5) if ARGV.infile: send(ARGV) else: recv(ARGV)