diff --git a/README.md b/README.md index 135e709..987b5d9 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ To avoid using unstable or too recent libraries, the rule of thumb is that a lib * [BeautifulSoup4](https://www.crummy.com/software/BeautifulSoup) and [Readability](https://github.com/buriy/python-readability) are both needed to render HTML. Without them, HTML will not be rendered or be sent to an external parser like Lynx. (apt-get install python3-bs4 python3-readability or pip3 install readability-lxml) * [Python-feedparser](https://github.com/kurtmckee/feedparser) will allow parsing of RSS/Atom feeds and thus subscriptions to them. (apt-get install python3-feedparser) * The [ansiwrap library](https://pypi.org/project/ansiwrap/) may result in - neater display of text which makes use of ANSI escape codes to control colour (not in Debian?). + neater display of text which makes use of ANSI escape codes to control colour. Ansiwrap is also required to display pictures in HTML pages (together with Chafa and python-pil) (not in Debian?). * The [cryptography library](https://pypi.org/project/cryptography/) will provide a better and slightly more secure experience when using the default TOFU certificate validation mode and is highly recommended (apt-get install python3-cryptography). @@ -81,6 +81,7 @@ To avoid using unstable or too recent libraries, the rule of thumb is that a lib * [Python editor](https://github.com/fmoo/python-editor) is used to edit your lists with "list edit". (apt-get install python3-editor) * [Xsel](http://www.vergenet.net/~conrad/software/xsel/) allows to `go` to the URL copied in the clipboard without having to paste it (both X and traditional clipboards are supported). Also needed to use the `copy` command. (apt-get install xsel) * [Chafa](https://hpjansson.org/chafa/) allows to display pictures in your console. Install it and browse to an HTML page with picture to see the magic. +* [Python-pil](http://python-pillow.github.io/) is required to only display the first frame of animated gif with chafa. ## Features diff --git a/offpunk.py b/offpunk.py index 3e0c5e7..4739b03 100755 --- a/offpunk.py +++ b/offpunk.py @@ -52,9 +52,23 @@ except ModuleNotFoundError: try: import ansiwrap as textwrap + _HAS_ANSIWRAP = True except ModuleNotFoundError: print("Try installing python-ansiwrap for better rendering") import textwrap + _HAS_ANSIWRAP = False + +try: + from PIL import Image + if _HAS_ANSIWRAP and shutil.which('chafa'): + _RENDER_IMAGE = True + else: + print("chafa and ansiwrap are required to render images in terminal") + _RENDER_IMAGE = False +except ModuleNotFoundError: + print("python-pil, chafa and ansiwrap are required to render images") + _RENDER_IMAGE = False + try: from cryptography import x509 @@ -586,12 +600,16 @@ class HtmlRenderer(): src = element.get("src") text = "" ansi_img = "" - if shutil.which('chafa'): + if _RENDER_IMAGE: abs_url = urllib.parse.urljoin(self.url, src) g = GeminiItem(abs_url) if g.is_cache_valid(): img = g.get_cache_path() - return_code = subprocess.run("chafa --bg white -s 40 %s"%img, \ + img_obj = Image.open(img) + if hasattr(img_obj,"n_frames") and img_obj.n_frames > 1: + # we remove all frames but the first one + img_obj.save(img,save_all=False) + return_code = subprocess.run("chafa --bg white -s 40 %s -w 1"%img, \ shell=True, capture_output=True) ansi_img = return_code.stdout.decode() alt = element.get("alt") @@ -644,8 +662,14 @@ class HtmlRenderer(): i_indent = "" s_indent = i_indent if line.strip() != "": - wrapped = textwrap.fill(line,width,initial_indent=i_indent, + try: + wrapped = textwrap.fill(line,width,initial_indent=i_indent, subsequent_indent=s_indent) + except Exception as err: + wrapped = line + #print(self.url) + #print(err) + #crash wrapped += "\n" else: wrapped = "" @@ -1254,7 +1278,7 @@ class GeminiClient(cmd.Cmd): else: return elif gi.scheme in ("gopher"): - gi = self._fetch_gopher(gi) + gi = self._fetch_gopher(gi,timeout=self.options["short_timeout"]) else: gi = self._fetch_over_network(gi) except UserAbortException: @@ -1340,7 +1364,9 @@ class GeminiClient(cmd.Cmd): gi.write_body(body,mime) return gi - def _fetch_gopher(self,gi): + def _fetch_gopher(self,gi,timeout=10): + if not looks_like_url(gi.url): + print("%s is not a valide url" %gi.url) parsed =urllib.parse.urlparse(gi.url) host = parsed.hostname port = parsed.port or 70 @@ -1356,7 +1382,22 @@ class GeminiClient(cmd.Cmd): else: itemtype = "1" selector = parsed.path + addresses = socket.getaddrinfo(host, port, family=0,type=socket.SOCK_STREAM) s = socket.create_connection((host,port)) + for address in addresses: + self._debug("Connecting to: " + str(address[4])) + s = socket.socket(address[0], address[1]) + s.settimeout(timeout) + try: + s.connect(address[4]) + break + except OSError as e: + err = e + else: + # If we couldn't connect to *any* of the addresses, just + # bubble up the exception from the last attempt and deny + # knowledge of earlier failures. + raise err if parsed.query: request = selector + "\t" + parsed.query else: