import json from glob import glob from time import time_ns import datetime import os import sys from subprocess import run import tempfile from math import floor home = os.path.expanduser("~/.bink") max_body_length = 64_000 helptext = """see https://git.tilde.town/nebula/bink for details --help: show this message --pipe: user stdin as post content. ex `echo "hello!" | bink.py --pipe` --dump: print all posts in a json object""" try: os.mkdir(home) except FileExistsError: pass try: editor = os.environ["EDITOR"] except KeyError: editor = "nano" def create_post(body): with open(f"{home}/{time_ns()}", "w", encoding="UTF-8") as f: f.write(body) def file_object(path): split = path.split("/") # example input: /home/nebula/.bink/999 # output: (999, "nebula", "/home/nebula/.bink/999") return (int(split[-1]), split[2], path) def generate_feed(before=None, count=200): posts = [file_object(path) for path in glob("/home/**/.bink/*")] if before: posts = [post for post in posts if post[0] < before] posts.sort(key=lambda x: x[0], reverse=True) blogs = [] for time, user, path in posts[:count]: # try: with open(path, "r", encoding="UTF-8") as f: body = f.read() # except obj = { "user": user, "time": time, "body": body[:max_body_length], } blogs.append(obj) return blogs if len(sys.argv) > 1: if sys.argv[1] == "--help": exit(helptext) elif sys.argv[1] == "--dump": with open("/dev/stdout", "w") as f: json.dump(generate_feed(), f) exit() elif sys.argv[1] == "--pipe": try: with open("/dev/stdin", "r", encoding="UTF-8") as f: body = f.read().strip() if body: create_post(body) exit() except KeyboardInterrupt: exit() else: create_post(" ".join(sys.argv[1:])) exit() import urwid footer = "[c]reate new post [r]efresh [q]uit | scrolling: arrows, space, page up/page down, ctrl-d/ctrl-u, j/k" attrmap = [ ("bold", "default,bold", "default"), ("reverse", "standout", "default") ] class App(): def __init__(self): self.walker = urwid.SimpleFocusListWalker([ self.post_to_widget(post) for post in generate_feed() ]) self.loop = urwid.MainLoop( urwid.Frame( ActionBox(self.walker), footer=urwid.Text(("reverse", footer)) ), palette=attrmap ) def update(self, before=None, clear=True): if clear: self.walker.clear() for post in generate_feed(before=before): self.walker.append(self.post_to_widget(post)) def post_to_widget(self, post): time_seconds = post["time"] / 1_000_000_000 stamp = datetime.datetime.fromtimestamp(time_seconds) pile = urwid.Pile([ urwid.Text([("bold", f"~{post['user']}"), " @ ", stamp.strftime("%H:%M (%A, %B %d, %Y)")]), urwid.Text(post['body']), urwid.Divider() ]) # pile.post_time = post["time"] return pile def write_with_editor(self): self.loop.stop() tmp = tempfile.NamedTemporaryFile() run([editor, tmp.name]) with open(tmp.name, "r") as f: body = f.read().strip() if body: create_post(body) self.update() self.loop.start() def exit(self, message=""): app.loop.stop() run(["clear"]) exit(message) class ActionBox(urwid.ListBox): def keypress(self, size, key): keyl = key.lower() if keyl == "c": app.write_with_editor() elif keyl == "r": app.update() elif keyl in ("q", "x"): app.exit() elif keyl == " ": super().keypress(size, "page down") elif keyl in ("j", "n", "ctrl n"): super().keypress(size, "down") elif keyl in ("k", "p", "ctrl p"): super().keypress(size, "up") elif key == "g": super().keypress(size, "home") elif key == "G": super().keypress(size, "end") elif key == "ctrl d": for i in range(1, floor(size[1] / 2)): super().keypress(size, "down") elif key == "ctrl u": for i in range(1, floor(size[1] / 2)): super().keypress(size, "up") super().keypress(size, key) # completely borked code # focus = self.get_focus_widgets()[0] # if focus.post_time == app.walker[-1].post_time: # app.update(before=focus.post_time) app = App() try: app.loop.run() except KeyboardInterrupt: app.exit()