--- /dev/null
+#!/usr/bin/env python3
+
+'''
+Textual Mastodon client with a UI inspired by Monochrome BBS.
+'''
+
+import argparse
+import calendar
+import requests
+import string
+import time
+
+import text
+
+class HTTPError(Exception):
+ def __init__(self, response):
+ self.response = response
+
+ def __str__(self):
+ return (f"{self.response.request.method} request "
+ f"for {self.response.request.url} "
+ f"returned status {self.response.status_code}")
+
+class Client:
+ def __init__(self, instance):
+ self.base_url = instance + "/api/v1/"
+ self.log_response = lambda *args, **kws: None
+
+ def enable_debug(self, logfile):
+ logfh = open(logfile, "w")
+ pr = lambda *args, **kws: print(*args, file=logfh, **kws)
+
+ def log_response(rsp):
+ pr("Request: {rsp.request.method} {rsp.request.url}")
+ pr(" Response status: {rsp.status_code}")
+ pr(" Response headers:")
+ for k, v in rsp.headers.items():
+ pr(f" {k}: {v}")
+
+ self.log_response = log_response
+
+ def get_public(self, path, **params):
+ rsp = requests.get(self.base_url + path, params=params)
+ self.log_response(rsp)
+ if rsp.status_code != 200:
+ raise HTTPError(rsp)
+ return rsp.json()
+
+class Post:
+ def __init__(self, data):
+ self.post_id = data['id']
+
+ date, suffix = data['created_at'].split(".", 1)
+ if suffix.lstrip(string.digits) != "Z":
+ raise ValueError(f"{self.post_id}: bad creation date {date!r}")
+ tm = time.strptime(date, "%Y-%m-%dT%H:%M:%S")
+ self.datestamp = calendar.timegm(tm)
+
+ def text(self):
+ yield text.SeparatorLine(self.datestamp)
+
+class MainUI(Client):
+ def run(self):
+ for item in self.get_public("timelines/public", limit=10):
+ p = Post(item)
+ for thing in p.text():
+ print(thing.render(79))
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument("--log", help="File to log debug information to.")
+ parser.set_defaults(action=MainUI)
+ args = parser.parse_args()
+
+ instance = "hachyderm.io" # FIXME
+ if "://" not in instance:
+ instance = "https://" + instance
+
+ thing = args.action(instance)
+ if args.log is not None:
+ thing.enable_debug(args.log)
+ thing.run()
+
+if __name__ == '__main__':
+ main()
--- /dev/null
+# Represent colourised terminal text in a width-independent form.
+
+import time
+
+class SeparatorLine:
+ def __init__(self, timestamp):
+ self.timestamp = timestamp
+
+ def render(self, width):
+ date = time.strftime("%a %b %e %H:%M:%S %Y",
+ time.localtime(self.timestamp))
+ # FIXME: colours
+ suffix = "[" + date + "]--"
+ return "-" * (width - len(suffix)) + suffix