#!/usr/bin/env python3 import cgi import mailcap import os import socket import ssl import tempfile import textwrap import urllib.parse caps = mailcap.getcaps() menu = [] hist = [] def printHelp(): help = """ Type in a gemini:// address to go somewhere, input 'b' to go back a page, 'f' to go forward, 'h' to show your history in order, '?' to show this help again, or 'q' to quit. """ print(help) def printIntro(): o = """ Welcome to Asparagus, a single-file Python client for the Gemini protocol. Original source code by solderpunk, and forked by Wholesomedonut on Sep 6 2020. Questions about this fork? Email with subject "Asparagus + whatever-you-need", to wholesomedonut[at]tuta[dot]io . """ print(o) printHelp() def absolutise_url(base, relative): # Absolutise relative links if "://" not in relative: # Python's URL tools somehow only work with known schemes? base = base.replace("gemini://","http://") relative = urllib.parse.urljoin(base, relative) relative = relative.replace("http://", "gemini://") return relative printIntro() while True: # Get input cmd = input("> ").strip() # Handle things other than requests if cmd.lower() == "q": print("Bye!") break # Get URL, from menu, history or direct entry if cmd.isnumeric(): url = menu[int(cmd)-1] elif cmd.lower() == "b": # Yes, twice # url = hist.pop() # url = hist.pop() # updated to preserve history in list # so you can use 'f' to go forward too. - WD try: url = hist[len(hist) - 2] except IndexError as e: print(e) print('You do not have any history to go back to') continue elif cmd.lower() == "f": # now you can go ahead in history too! - WD try: url = hist[url+1] except NameError as e: print(e) print('You do not have any history') except IndexError as e: print(e) print('You are at the front of your history already') continue elif cmd.lower() == "h": # Will actually show history. - WD try: print("History:") for x in range(len(hist)): print('[',x,']',hist[x]) except Exception as e: print(e) print('Looks like I dun goofed.') elif cmd == "?": try: printHelp() continue except Exception as e: print(e) else: url = cmd if not "://" in url: url = "gemini://" + url # this try/except allows you to check for history with 'h' # even if you have visited no pages yet, without crashing. - WD try: parsed_url = urllib.parse.urlparse(url) except NameError as e: print('Error:',e,'\nso try having some history first by visiting a page') continue if parsed_url.scheme != "gemini": print("Sorry, Gemini links only.") # break continue # now it'll just loop again # instead of quitting the program if you # put the wrong thing in. - WD # Do the Gemini transaction try: while True: s = socket.create_connection((parsed_url.netloc, 1965)) context = ssl.SSLContext() context.check_hostname = False context.verify_mode = ssl.CERT_NONE s = context.wrap_socket(s, server_hostname = parsed_url.netloc) s.sendall((url + '\r\n').encode("UTF-8")) # Get header and check for redirects fp = s.makefile("rb") header = fp.readline() header = header.decode("UTF-8").strip() status, mime = header.split() # Handle input requests if status.startswith("1"): # Prompt query = input("INPUT" + mime + "> ") url += "?" + urllib.parse.quote(query) # Bit lazy... # Follow redirects elif status.startswith("3"): url = absolutise_url(url, mime) parsed_url = urllib.parse.urlparse(url) # Otherwise, we're done. else: break except Exception as err: print(err) continue # Fail if transaction was not successful if not status.startswith("2"): print("Error %s: %s" % (status, mime)) # haha funny error code - WD print("OH NO I didn't communicate with the Gemini server right") continue # Handle text if mime.startswith("text/"): # Decode according to declared charset mime, mime_opts = cgi.parse_header(mime) body = fp.read() body = body.decode(mime_opts.get("charset","UTF-8")) # Handle a Gemini map if mime == "text/gemini": menu = [] preformatted = False for line in body.splitlines(): if line.startswith("```"): preformatted = not preformatted elif preformatted: print(line) elif line.startswith("=>") and line[2:].strip(): bits = line[2:].strip().split(maxsplit=1) link_url = bits[0] link_url = absolutise_url(url, link_url) menu.append(link_url) text = bits[1] if len(bits) == 2 else link_url print("[%d] %s" % (len(menu), text)) else: print(textwrap.fill(line, 80)) # Handle any other plain text else: print(body) # Handle non-text else: tmpfp = tempfile.NamedTemporaryFile("wb", delete=False) tmpfp.write(fp.read()) tmpfp.close() cmd_str, _ = mailcap.findmatch(caps, mime, filename=tmpfp.name) os.system(cmd_str) os.unlink(tmpfp.name) # Update history hist.append(url)