From c0186a28ab287f163fea7cd35cea16340da16a3f Mon Sep 17 00:00:00 2001 From: 4c3e <97980799+4c3e@users.noreply.github.com> Date: Wed, 4 Jan 2023 23:14:16 -0500 Subject: [PATCH] init --- CHANGELOG | 188 ++ LICENSE | 25 + README.md | 123 ++ RRTPRequest.py | 180 ++ debian/changelog | 12 + debian/clean | 1 + debian/control | 29 + debian/copyright | 41 + debian/docs | 2 + debian/offpunk.install | 1 + debian/rules | 20 + debian/scripts/offpunk | 10 + debian/source/format | 1 + doc/config.gmi | 0 doc/dev.gmi | 0 doc/index.gmi | 17 + doc/install.gmi | 0 doc/lists.gmi | 0 doc/offline.gmi | 0 doc/shell.gmi | 0 doc/tutorial.gmi | 0 make_deb.sh | 2 + offpunk.py | 4450 +++++++++++++++++++++++++++++++++++++++ requirements.txt | 8 + screenshot_offpunk1.png | Bin 0 -> 84970 bytes screenshot_offpunk2.png | Bin 0 -> 83074 bytes setup.py | 23 + ubuntu_dependencies.txt | 1 + 28 files changed, 5134 insertions(+) create mode 100644 CHANGELOG create mode 100644 LICENSE create mode 100644 README.md create mode 100644 RRTPRequest.py create mode 100644 debian/changelog create mode 100644 debian/clean create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100644 debian/offpunk.install create mode 100644 debian/rules create mode 100644 debian/scripts/offpunk create mode 100644 debian/source/format create mode 100644 doc/config.gmi create mode 100644 doc/dev.gmi create mode 100644 doc/index.gmi create mode 100644 doc/install.gmi create mode 100644 doc/lists.gmi create mode 100644 doc/offline.gmi create mode 100644 doc/shell.gmi create mode 100644 doc/tutorial.gmi create mode 100644 make_deb.sh create mode 100644 offpunk.py create mode 100644 requirements.txt create mode 100644 screenshot_offpunk1.png create mode 100644 screenshot_offpunk2.png create mode 100644 setup.py create mode 100644 ubuntu_dependencies.txt diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..dd80e76 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,188 @@ +# Offpunk History + +## 1.9 - unreleased +- Default handlers have been removed (not everybody use feh and zathura) +- Fix a crash when subscribing without GI (reported by sodimel on linuxfr) +- Fix a crash when trying to access a link without GI (Ben Winston) + +## 1.8 - December 11th 2022 +- Official URL is now https://sr.ht/~lioploum/offpunk/ +- SECURITY: Avoid passing improperly-escaped paths to shell (fixes notabug #9) (by Maeve Sproule) +- Add support for the finger protocol (by Sotiris Papatheodorou) +- "restricted" mode has been removed because unmaintained (code cleanup) +- "set accept_bad_ssl_certificates True" allows to lower HTTPS SSL requirements (also with --assume-yes) +- Accept "localhost" as a valid URL +- Better feedback when --sync an URL which is streaming +- Removed cgi dependency (soon deprecated) +- Fix: crash with some svg data:image (which are now ignored) +- Fix images from "full" mode not being downloaded +- Fix a crash when ls on empty page (thanks Marty Oehme) +- Fix: A variable was not initialised without python-cryptography +- Fix: "cp raw" was not accessing the temp_file correctly +- Fix: ANSI handling off arrows in readline (by Ben Winston) + +## 1.7.1 - November 15th 2022 +- Correcting a stupid crash in search (thanks kelbot for the report) + +## 1.7 - November 15th 2022 +- New "search" command which uses kennedy.gemi.dev by default. +- New "wikipedia" command, which uses vault.transjovian.org by default. +- Aliases "wen", "wfr" and "wes" for Wikipedia in English, French and Spanish. +- Autocompletion for the list/add/move commands (that’s incredibly useful!) +- If a link is found in plain text in a gopher/gemini page, it is now + added to the list of links for that page. Useful for gopher. +- Create system lists when needed to avoid failure on clean system +- Solve a crash when parsing wrong URL (related to bug #9 ) +- Solve a crash when loading webpages with empty links +- Solve a crash when trying to load a wrong URL into tour +=> gemini://ploum.be/2022-11-15-offpunk17-sourcehut.gmi + +## 1.6 - October 12th 2022 +- Support for base64 encoded pictures in HTML pages (opening them full screen only works offline) +- A list can be added to a tour with "tour $LIST_NAME". +- Check for timg > 1.3.2 to avoid dealing with old versions (bug reported by Valvin) +- Redirect are now honoured also when --sync (bug #15, thanks kelbot) +- RSS feeds are now automatically downloaded with a webpage (bug #14) +- Solved the bug where an invalid URL would break correspondance between url and numbers +- Considers .xml files as feed by default to avoid false-detection as SVG +- Replaced default libreddit.com redirection to teddit.net (bug #12 by kelbot) +- The "beta" option has been removed as it is not used (update your config if needed) + +## 1.5 - August 4th 2022 +- Removed optional dependency to ripgrep. "grep --color=auto" is good enough. +- "open url" to open current URL in a browser with xdg-open +- "redirect" now replaces "set redirects" to improve discoverability +- "redirect" now allows urls to be blocked. By default, facebook.com and google-analytics.com are blocked +- Fixed a bug when trying to download base64 image +=> gemini://rawtext.club/~ploum/2022-08-04-offpunk15.gmi + +## 1.4 - April 25th 2022 +- Making python-readability optional +- Removing "next" and "previous" which are quite confusing and not obvious +- Archiving now works regardless of the view you are in. +- Fixing a crash when accessing an empty html page +- Not trying to display non-image files to avoid errors. (this requires "file") + +## 1.3 - April 2th 2022 +- Removed dependency to python-magic. File is now used directly (and should be on every system). +- Removed dependency to python-editor. If no $VISUAL or $EDITOR, please use "set editor" in Offpunk. +- Images are now downloaded before displaying an HTML page (can be disabled with "set download_images_first False") +- Introduced "set redirects" which redirects twitter,youtube,medium,reddit to alternative frontends. +- New behaviour for "find" (or "/") which is to grep through current page (ripgrep used if detected) +- Default width set to 80 as many gopherholes and gemini capsules have it hardcoded +- Streaming URL without valid content-length are now closed after 5Mo of download (thanks to Eoin Carney for reporting the issue) +- Gif animations are now displayed once when viewed (instead of a still frame). +- Restored some AV-98 certificate validation code that was lost I don’t know how. +- Improved clarity of dependencies in "version" +- Fixed a crash when the cache is already a dir inside a dir. +- Fixed a crash when manually entering an unknown gopher URL while offline +- Fixed an error with older less version +- Fixed bookmarks not being automatically created at first "add" +- Call to shell commands has been refactorised to improve compatibility with python 3.6 (with testing from Pelle Nilsson) +- requirements.txt has been contributed by Toby Kurien. Thanks! +=> gemini://rawtext.club/~ploum/2022-04-02-offpunk13.gmi + +## 1.2 - March 24th 2022 +Very experimental release: +- Completely rewritten the HMTL, Gemtext and Gopher renderer. Tests needed! +- Removed dependancy to ansiwrap. We don’t use it anymore (which is an important achievement) +- Lists are now accessed via the protocol "list://". +- "view full" can now be bookmarked/synchronized as a separate entity. +- "view normal" introduced to get back to the normal view. +Small improvements: +- Limit width of --sync output +- Solved list names becoming very long in the history +- Fixed a crash when trying to save a folder +=> gemini://rawtext.club/~ploum/2022-03-24-ansi_html.gmi + +## 1.1 - March 18th 2022 +- Perfect rendering of pictures with chafa 1.8+ and compatible terminal (Kitty) +- timg is supported as an alternative to chafa (with a little glitch) +- "cp cache" put the path of the cached content in clipboard +- "cp url X" will copy the URL of link X (suggested by Eoin Carney) +- "fold" has been removed as it doesn’t work well and can be replaced with "!fold". +- Improved clipboard URL detection an fixed crash when binary in clipboard +- HTML: renderering of
 has been improved
+- HTML: links in titles were previously missed
+- Fixed crash when chafa is not installed (Thanks Xavier Hinault for the report)
+- Fixed crash when python-readability not installed (Thanks Nic for the report)
+- Fixed some gif not being displayed
+- Fixed some URL being wronlgy interpreted as IPv6
+
+## 1.0 - March 14th 2022
+- Default width is now the standard 72
+- Content and pictures now centered for more elegant reading
+- "less" has been renamed "view"
+- "view feed" and "view feeds" to see the first/all feeds on a HTML page
+- "view full" has been improved by dropping inline CSS and JS.
+- "up" can now take integer as argument to go up multiple steps.
+- Fixed a crash when accessing links in list (thanks Matthieu Talbot for the report)
+- Fixed a crash in "info" due to a typo in a variable name rarely accessed.
+- Removed dependancy to python-xdg by implementing the logic (which saved lines of code!)
+- python-pil is only needed if chafa < 1.10
+=> gemini://rawtext.club/~ploum/2022-03-14-offpunk_and_cyberpunk.gmi
+
+## 0.9 - March 05th 2022
+- Initial Spartan protocol support
+- Http links with content above 20Mo are not downloaded during sync (except when explicitely requested)
+- Improving subscriptions with more feedback and better detection
+- Avoid deprecated SSL methods (thanks Phoebos for the report)
+- Links in to_fetch are fetched, no matter the cache
+- Fixed multiple crashes
+=> gemini://rawtext.club/~ploum/2022-03-05-offpunk09.gmi
+
+## 0.4 - Feb 21st 2022
+UPGRADE: Users who subscribed to pages before 0.4 should run once the command "list subscribe subscribed". Without that, the subscribed list will be seen as a normal list by sync.
+- New list command : "list freeze" and "list suscribe"
+- Pictures are now displayed directely in terminal (suggested by kelbot)
+- "open" command to open current page/image/file with external handler.
+- "set width XX" now works to set the max width. If smaller, terminal width is used (thanks kelbot for reporting the bug)
+- RSS feeds are now rendered as Gemlogs to improve consistency while browsing
+- "subscribe" will detect feeds in html pages if any
+- "less" will restore previous position in a page (requires less 572+)
+- Improved syncing performances and multiple bug/crash fixes.
+- "version" will now display info about your system installation
+- "info" command will display technical information about current page
+- "sync" allows you to do the sync from within Offpunk
+=> gemini://rawtext.club/~ploum/2022-02-21-offpunk04.gmi
+
+## 0.3 - Feb 11th 2022
+New Features:
+- Gopher supported natively (early version, might have many bugs)
+- support for RSS and Atom feed (you can subscribe to them)
+- "less full" allows to see the full html page instead of only the article view
+ 	(also works with feeds to see descriptions of each post instead of a simple list)
+- Option --depth to customize your sync. Be warned, more than 1 is crazy.
+- Option --disable-http to allows deep syncing of gemini-only ressources
+- Vastly improved HTML rendering with support for images (you need the binary "chafa" on your system)
+Other Small Improvements:
+- Disabled https_everywhere by default (caching problems and some websites not supporting it)
+- Modified --sync logic to make it more intuitive (thanks Bjorn Westergard)
+- Caching more problems to avoid refetch
+- Offpunk has now an User-Agent when http browsing to avoid being blocked as a bot
+- Changed XDG logic to improve compatibility (thanks Klaus Alexander)
+=> gemini://rawtext.club/~ploum/2022-02-11-offpunk03.gmi
+
+## 0.2 - Jan 31st 2022
+- config directories have been moved to follow the XDG specifications
+- support for http, https and mailto links (https_everywhere is enabled by default, see "set" command)
+- support for HTML pages, rendered as articles
+- Mutiple bookmarks lists and management of them through commands list, add, archive, move
+- Subscriptions have been moved to a separate list with the subscribe command
+- History is persistent and saved to disk
+- Copy command allows to copy content or url into buffer
+- Search as been renamed find, in the hope of implementing a real search in the future
+- --fetch-later allows to mark a content to be fetched from other software.
+- --assume-yes allows to choose the default answer to certificates warnings during --sync.
+=> gemini://rawtext.club/~ploum/2022-01-31-offpunk02.gmi Announcing Offpunk 0.2
+
+## 0.1 - Jan 3rd 2022
+- initial release as an independant software from AV-98 (thanks solarpunk)
+- Including contributions published by Bjorn on Notabug (thanks ew0k)
+- less used by default for all content with custom options
+- online/offline mode
+- content is cached for offline use
+- bookmarks are cached and subscribed through the --sync option
+- tour is persistent and saved to disk
+- reload while offline mark the content to be fetched during next --sync
+=> gemini://rawtext.club/~ploum/2022-01-03-offpunk.gmi Announce of Offpunk 0.1
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7514d67
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2022, Ploum  and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..208d67d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,123 @@
+# OFFPUNK
+
+A command-line and offline-first smolnet browser/feed reader for Gemini, Gopher, Spartan and Web by [Ploum](https://ploum.net).
+
+The goal of Offpunk is to be able to synchronise your content once (a day, a week, a month) and then browse/organise it while staying disconnected.
+
+Official project page (repository/mailing lists) : https://sr.ht/~lioploum/offpunk/
+
+![Screenshot HTML page with picture](screenshot_offpunk1.png)
+![Screenshot Gemini page](screenshot_offpunk2.png)
+
+Offpunk is a fork of the original [AV-98](https://tildegit.org/solderpunk/AV-98) by Solderpunk and was originally called AV-98-offline as an experimental branch.
+
+## How to use
+
+Offpunk is a single python file. Installation is optional, you can simply download and run "./offpunk.py" or "python3 offpunk.py" in a terminal.
+
+You use the `go` command to visit a URL, e.g. `go gemini.circumlunar.space`. (gemini:// is assumed if no protocol is specified. Supported protocols are gemini, gopher, finger, http, https, mailto, spartan and file).
+
+Links in pages are assigned numerical indices.  Just type an index to follow that link. If page is too long to fit on your screen, the content is displayed in the less pager (by default). Type `q` to quit and go back to Offpunk prompt. Type `view` or `v` to display it again. (`view full` or `v full` allows to see the full html page instead of the article view. `v feed` try to display the linked RSS feed and `v feeds` displays a list of available feeds. This only applies to html pages)
+
+Use `add` to add a capsule to your bookmarks and `bookmarks` or `bm` to show your bookmarks (you can create multiple bookmarks lists, edit and remove them. See the `list` manual with `help list`).
+
+Use `offline` to only browse cached content and `online` to go back online. While offline, the `reload` command will force a re-fetch during the next synchronisation.
+
+Use the `help` command to learn about additional commands. Some abreviations are available. See `abbrevs`.
+
+When launched with the "--sync" option, offpunk will run non-interactively and fetch content from your bookmarks, lists and ressources tentatively accessed while offline. New content found in your subscriptions (see `help subscribe`) will be automatically added to your tour (use `tour ls` to see your current tour, `tour` without argument to access the next item and `tour X` where X is a link number to add the content of a link to your tour).
+
+With "--sync", one could specify a "--cache validity" in seconds. This option will not refresh content if a cache exists and is less than the specified amount of seconds old.
+
+For example, running
+
+`offpunk --sync --cache-validity 43200`
+
+will refresh your bookmarks if those are at least 12h old. If cache-validity is not set or set to 0, any cache is considered good and only content never cached before will be fetched. `--assume-yes` will automatically accept SSL certificates with errors instead of refusing them.
+
+Offpunk can also be configured as a browser by other tool. If you want to use offpunk directly with a given URL, simply type:
+
+`offpunk URL`
+
+To have offpunk fetch the URL at next sync and close immediately, run:
+
+`offpunk --fetch-later URL`
+
+## More
+
+Important news and releases will be announced on the offpunk-devel mailing list 
+=> https://lists.sr.ht/~lioploum/offpunk-devel
+
+Questions can be asked on the users mailing list:
+=> https://lists.sr.ht/~lioploum/offpunk-users
+
+## Dependencies
+
+Offpunk has no "strict dependencies", i.e. it should run and work without anything
+else beyond the Python standard library and the "less" pager.  However, it will "opportunistically
+import" a few other libraries if they are available to offer an improved
+experience or some other features. Python libraries requests, bs4 and readability are required for http/html support. Images are displayed if chafa or timg are presents (python-pil is needed for chafa version before 1.10). When displaying only a picture (not inline), rendering will be pixel perfect in compatible terminals (such as Kitty) if chafa is at least version 1.8 or if timg is used.
+
+To avoid using unstable or too recent libraries, the rule of thumb is that a library should be packaged in Debian/Ubuntu. Keep in mind that Offpunk is mainly tested will all libraries installed. If you encounter a crash without one optional dependencies, please report it. Patches and contributions to remove dependencies or support alternatives are highly appreciated.
+
+* PIP: [requirements file to install dependencies with pip](requirements.txt)
+* Ubuntu/Debian: [command to install dependencies  on Ubuntu/Debian without pip](ubuntu_dependencies.txt)
+* Arch: [AUR package for Arch Linux, maintained by kseistrup](https://aur.archlinux.org/packages/offpunk-git)
+* [Nix](https://nixos.org/): [package](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/networking/browsers/offpunk/default.nix), maintained by [DamienCassou](https://github.com/DamienCassou)
+* Alpine Linux: [package maintained by mio](https://pkgs.alpinelinux.org/packages?name=offpunk)
+* Please contribute packages for other systems, there’s a [mailing-list dedicated to packaging](https://lists.sr.ht/~lioploum/offpunk-packagers).
+
+Run command `version` in offpunk to see if you are missing some dependencies.
+
+Mandatory or highly recommended (packagers should probably make those mandatory):
+* [less](http://www.greenwoodsoftware.com/less/): mandatory but is probably already on your system
+* [file](https://www.darwinsys.com/file/) is used to get the MIME type of cached objects. Should already be on your system.
+* [xdg-utils](https://www.freedesktop.org/wiki/Software/xdg-utils/) provides xdg-open which is highly recommended to open files without a renderer or a handler. It is also used for mailto: command.
+* 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 recommended (apt-get install python3-cryptography).
+
+Dependencies to enable web browsing (packagers may put those in an offpunk-web meta-package but it is recommended to have it for a better offpunk experience)
+* [Python-requests](http://python-requests.org) is needed to handle http/https requests natively (apt-get install python3-requests). Without it, http links will be opened in an external browser
+* [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)
+* [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.
+* [Timg](https://github.com/hzeller/timg) is a slower alternative to chafa for inline images. But it has better rendering when displaying only the image. Install both to get the best of both world but if you need to choose one, choose Chafa. 
+* [Python-pil](http://python-pillow.github.io/) is required to only display the first frame of animated gif with chafa if chafa version is lower than 1.10. 
+
+Nice to have (packagers should may make those optional):
+* [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)
+* [Python-setproctitle](https://github.com/dvarrazzo/py-setproctitle) will change the process name from "python" to "offpunk". Useful to kill it without killing every python service.
+
+## Features
+
+* Browse https/gemini/gopher/spartan without leaving your keyboard and without distractions
+* Built-in documentation: type `help` to get the list of command or a specific help about a command.
+* Offline mode to browse cached content without a connection. Requested elements are automatically fetched during the next synchronization and are added to your tour.
+* HTML pages are prettified to focus on content. Read without being disturbed or see the full page with `view full`.
+* RSS/Atom feeds are automatically discovered by `subscribe` and rendered as gemlogs. They can be explored with `view feed` and `view feeds`.
+* Support "subscriptions" to a page. New content seen in subscribed pages are automatically added to your next tour.
+* Complex bookmarks management through multiple lists, built-in edition, subscribing/freezing lists and archiving content.
+* Advanced navigation tools like `tour` and `mark` (as per VF-1). Unlike AV-98, tour is saved on disk accross sessions. 
+* Ability to specify external handler programs for different MIME types (use `handler`)
+* Enhanced privacy with `redirect` which allows to block a http domain or to redirect all request to a privacy friendly frontent (such as nitter for twitter).
+* Non-interactive cache-building with configurable depth through the --sync command. The cache can easily be used by other software. 
+* IPv6 support
+* Supports any character encoding recognised by Python
+* Cryptography : TOFU or CA server certificate validation
+* Cryptography : Extensive client certificate support if an `openssl` binary is available
+
+## RC files
+
+You can use an RC file to automatically run any sequence of valid Offpunk
+commands upon start up.  This can be used to make settings controlled with the
+`set` or `handler` commanders persistent.  You can also put a `go` command in
+your RC file to visit a "homepage" automatically on startup, or to pre-prepare
+a `tour` of your favourite Gemini sites or `offline` to go offline by default.
+
+The RC file should be called `offpunkrc` and goes in $XDG_CONFIG_DIR/offpunk (or .config/offpunk or .offpunk if xdg not available). In that file, simply write one command per line, just like you would type them in offpunk. 
+
+## Cache design
+
+The offline content is stored in ~/.cache/offpunk/ as plain .gmi/.html files. The structure of the Gemini-space is tentatively recreated. One key element of the design is to avoid any database. The cache can thus be modified by hand, content can be removed, used or added by software other than offpunk.
+
+There’s no feature to automatically trim the cache. But part of the cache can safely be removed manually.
+
diff --git a/RRTPRequest.py b/RRTPRequest.py
new file mode 100644
index 0000000..75f2cfd
--- /dev/null
+++ b/RRTPRequest.py
@@ -0,0 +1,180 @@
+import time
+import RNS
+import os
+import urllib
+
+
+class RRTPResponseObject:
+    response: bytes
+    status: str
+    type: str
+    meta: str
+    body: bytes
+    ok: bool
+
+    def __init__(self):
+        self.response = None
+        self.status = ""
+        self.type = ""
+        self.meta = ""
+        self.body = None
+        self.ok = False
+
+
+def parse_url(url):
+    if "://" not in url:
+        url = "rrtp://" + url
+    t_url = url.replace("rrtp://", "http://")
+    parsed = urllib.parse.urlparse(t_url)
+    path = parsed.path
+    if path == "":
+        path = "/"
+    return parsed.netloc, path
+
+
+def request_failed(request_receipt):
+    RNS.log("The request " + RNS.prettyhexrep(request_receipt.request_id) + " failed.")
+
+
+def link_closed(link):
+    if link.teardown_reason == RNS.Link.TIMEOUT:
+        RNS.log("The link timed out, exiting now")
+    elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
+        RNS.log("The link was closed by the server, exiting now")
+    else:
+        RNS.log("Link closed, exiting now")
+
+    RNS.Reticulum.exit_handler()
+    time.sleep(1.5)
+    os._exit(0)
+
+
+class RRTPRequest:
+    def link_established(self, link):
+        self.link = link
+        RNS.log("Link established with server")
+
+    def handle_response(self, request_receipt):
+        raw_response = request_receipt.response
+        self.responded = True
+        self.response = RIPResponseObject()
+        header = raw_response[0]
+        header = header.split(" ", maxsplit=2)
+        self.response.status = header[0]
+        self.response.type = header[1]
+        if len(header) > 2:
+            self.response.meta = header[2]
+
+        if raw_response[1]:
+            self.response.body = raw_response[1]
+
+        self.response.ok = True
+
+    def blocking_request(self, path, data=None):
+        self.responded = False
+        self.response = None
+        self.status = ""
+        self.type = ""
+        self.meta = ""
+        self.ok = False
+        try:
+            RNS.log("Sending request to " + path)
+            self.link.request(
+                path,
+                data=data,
+                response_callback=self.handle_response,
+                failed_callback=request_failed,
+                timeout=5,
+            )
+
+        except Exception as e:
+            RNS.log("Error while sending request over the link: " + str(e))
+            self.curr_connected_dest = None
+            self.link.teardown()
+
+        while not self.responded:
+            time.sleep(0.1)
+
+        return
+
+    def req(self, url, data=None):
+        destination_hexhash, path = parse_url(url)
+
+        try:
+            if len(destination_hexhash) != 20:
+                raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
+            destination_hash = bytes.fromhex(destination_hexhash)
+
+        except:
+            RNS.log("Invalid destination entered. Check your input!\n")
+            exit()
+
+        if not RNS.Transport.has_path(destination_hash):
+            RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
+            RNS.Transport.request_path(destination_hash)
+            while not RNS.Transport.has_path(destination_hash):
+                time.sleep(0.1)
+
+        if self.curr_connected_dest == destination_hash and self.link:
+            self.blocking_request(path, data)
+            return self.response
+
+        server_identity = RNS.Identity.recall(destination_hash)
+
+        RNS.log("Establishing link with server...")
+
+        self.destination = RNS.Destination(
+            server_identity,
+            RNS.Destination.OUT,
+            RNS.Destination.SINGLE,
+            "rrtp",
+            "server"
+        )
+
+        t_link = RNS.Link(self.destination)
+
+        t_link.set_link_established_callback(self.link_established)
+        t_link.set_link_closed_callback(self.destination)
+
+        while not self.link:
+            time.sleep(0.1)
+
+        self.curr_connected_dest = destination_hash
+
+        self.blocking_request(path, data)
+        return self.response
+
+    def get(self, url, params=None):
+        return self.req(url)
+
+    def post(self, url, data, params=None):
+        return self.req(url, data)
+
+    def __init__(self, identity=None):
+        r = RNS.Reticulum()
+        self.identity = None
+        self.destination = None
+        self.link = None
+        self.curr_connected_dest = None
+        self.responded = False
+        # raw response
+        self.response = None
+        # response status code
+        self.status = ""
+        # mime type of response
+        self.type = ""
+        # value of meta field
+        self.meta = ""
+        # response body
+        self.body = None
+        # response health
+        self.ok = False
+
+        if identity:
+            self.identity = identity
+        else:
+            self.identity = RNS.Identity()
+
+
+if __name__ == "__main__":
+    request = RRTPRequest()
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..950f255
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,12 @@
+offpunk (1.2.0-2) unstable; urgency=low
+
+  * Fix package mandatory and optional dependencies.
+  * Add project's docs.
+
+ -- Iván Ruvalcaba   Mon, 04 Apr 2022 17:33:00 -0500
+
+offpunk (1.2.0-1) unstable; urgency=low
+
+  * New upstream version.
+
+ -- Iván Ruvalcaba   Fri, 01 Apr 2022 12:15:00 -0500
diff --git a/debian/clean b/debian/clean
new file mode 100644
index 0000000..a3eb8a0
--- /dev/null
+++ b/debian/clean
@@ -0,0 +1 @@
+offpunk.egg-info/
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..f8b00aa
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,29 @@
+Source: offpunk
+Section: net
+Priority: optional
+Maintainer: Iván Ruvalcaba 
+Build-Depends:
+ debhelper-compat (= 13),
+ python3-all,
+Build-Depends-Indep:
+ dh-python,
+Rules-Requires-Root: no
+Standards-Version: 4.6.0
+Homepage: https://sr.ht/~lioploum/offpunk/
+
+Package: offpunk
+Architecture: all
+Depends:
+ less,
+ file,
+ xdg-utils,
+ python3-cryptography,
+ python3-feedparser,
+ python3-requests,
+ python3-bs4,
+ ${misc:Depends},
+ ${python3:Depends}
+Suggests: xsel, python3-setproctitle, python3-readability, chafa (>= 1.10.0), python3-pil
+Description: Offpunk is an offline-first browser for the smolnet
+ A command-line and offline-first smolnet browser/feed reader for Gemini,
+ Gopher, Spartan and Web.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..95f7c3a
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,41 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: offpunk
+Source: https://sr.ht/~lioploum/offpunk/
+Files: *
+Copyright: 2022 Ploum  and contributors
+License: BSD-2-Clause
+
+Files: debian/*
+Copyright: 2022 Iván Ruvalcaba 
+License: FSFAP
+
+License: BSD-2-Clause
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+License: FSFAP
+ Copying and distribution of this file, with or without modification, are
+ permitted in any medium without royalty, provided the copyright notice
+ and this notice are preserved. This file is offered as-is, without any
+ warranty.
+
+ See the GNU All-Permissive License for more details
+ .
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..f0f644e
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+LICENSE
+README.md
diff --git a/debian/offpunk.install b/debian/offpunk.install
new file mode 100644
index 0000000..6b02e32
--- /dev/null
+++ b/debian/offpunk.install
@@ -0,0 +1 @@
+debian/scripts/offpunk usr/bin
diff --git a/debian/rules b/debian/rules
new file mode 100644
index 0000000..881dd88
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,20 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+export PYBUILD_DESTDIR_python3=debian/offpunk/
+export LC_ALL=C.UTF-8
+
+%:
+	dh $@ --with python3 --buildsystem=pybuild
+
+override_dh_auto_build:
+	dh_auto_build
+
+override_dh_auto_install:
+	dh_auto_install
+	chmod 755 debian/offpunk/usr/bin/offpunk
+	rm debian/offpunk/usr/bin/offpunk
+	find debian -type d -empty -delete
+
+override_dh_installdocs:
+	dh_installdocs
diff --git a/debian/scripts/offpunk b/debian/scripts/offpunk
new file mode 100644
index 0000000..1469d50
--- /dev/null
+++ b/debian/scripts/offpunk
@@ -0,0 +1,10 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+import re
+import sys
+from offpunk import main
+
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/doc/config.gmi b/doc/config.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/doc/dev.gmi b/doc/dev.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/doc/index.gmi b/doc/index.gmi
new file mode 100644
index 0000000..fc852d7
--- /dev/null
+++ b/doc/index.gmi
@@ -0,0 +1,17 @@
+# OFFPUNK - An Offline-First Browser for the Smolnet
+
+Offpunk is a command-line browser and feed reader dedicated to browsing the Web, Gemini, Gopher and Spartan. Thanks to its permanent cache, it is optimised to be used offline with rare connections but works as well when connected.
+
+Offpunk is optimised for reading and supports readability mode, displaying pictures, subscribing to pages or RSS feeds, managing complex lists of bookmarks. Its integrated help and easy commands make it a perfect tool for command-line novices while power-users will be amazed by its shell integration.
+
+Offpunk is written in Python 3 by Ploum. It aims to be portable and minimise dependencies, making them optional. It supports http/https/gopher/gemini/spartan on both IPv4 and IPv6.
+
+
+=> install.gmi Installing Offpunk and dependencies
+=> tutorial.gmi First steps
+=> config.gmi Configuring Offpunk
+=> offline.gmi Using Offpunk offline
+=> lists.gmi Lists and subscriptions
+=> shell.gmi Shell and OS integrations
+=> dev.gmi Roadmap & Contributions
+
diff --git a/doc/install.gmi b/doc/install.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/doc/lists.gmi b/doc/lists.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/doc/offline.gmi b/doc/offline.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/doc/shell.gmi b/doc/shell.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/doc/tutorial.gmi b/doc/tutorial.gmi
new file mode 100644
index 0000000..e69de29
diff --git a/make_deb.sh b/make_deb.sh
new file mode 100644
index 0000000..3a87d06
--- /dev/null
+++ b/make_deb.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+dpkg-buildpackage -rfakeroot -uc -us
diff --git a/offpunk.py b/offpunk.py
new file mode 100644
index 0000000..dfa05c0
--- /dev/null
+++ b/offpunk.py
@@ -0,0 +1,4450 @@
+#!/usr/bin/env python3
+# Offpunk Offline Gemini client
+# Derived from AV-98 by Solderpunk,
+# (C) 2021, 2022 Ploum 
+# (C) 2019, 2020 Solderpunk 
+# With contributions from:
+#  - danceka 
+#  - 
+#  - 
+#  - Klaus Alexander Seistrup 
+#  - govynnus 
+#  - Björn Wärmedal 
+#  - 
+#  - Maeve Sproule 
+
+_VERSION = "1.8"
+
+import argparse
+import cmd
+import codecs
+import datetime
+import fnmatch
+import getpass
+import glob
+import hashlib
+import io
+import mimetypes
+import os
+import os.path
+import filecmp
+import random
+import shlex
+import shutil
+import socket
+import sqlite3
+import ssl
+from ssl import CertificateError
+import sys
+import tempfile
+import time
+import urllib.parse
+import uuid
+import webbrowser
+import html
+import base64
+import subprocess
+import RNS
+from RRTPRequest import RRTPRequest
+
+# In terms of arguments, this can take an input file/string to be passed to
+# stdin, a parameter to do (well-escaped) "%" replacement on the command, a
+# flag requesting that the output go directly to the stdout, and a list of
+# additional environment variables to set.
+def run(cmd, *, input=None, parameter=None, direct_output=False, env={}):
+    #print("running %s"%cmd)
+    if parameter:
+        cmd = cmd % shlex.quote(parameter)
+    env = dict(os.environ) | env
+    if isinstance(input, io.IOBase):
+        stdin = input
+        input = None
+    else:
+        if input:
+            input = input.encode()
+        stdin = None
+    if not direct_output:
+        # subprocess.check_output() wouldn't allow us to pass stdin.
+        result = subprocess.run(cmd, check=True, env=env, input=input,
+                                shell=True, stdin=stdin, stdout=subprocess.PIPE,
+                                stderr=subprocess.STDOUT)
+        return result.stdout.decode()
+    else:
+        subprocess.run(cmd, env=env, input=input, shell=True, stdin=stdin)
+
+try:
+    import setproctitle
+    setproctitle.setproctitle("offpunk")
+    _HAS_SETPROCTITLE = True
+except ModuleNotFoundError:
+    _HAS_SETPROCTITLE = False
+
+import textwrap
+
+global TERM_WIDTH
+TERM_WIDTH = 80
+
+def term_width():
+    width = TERM_WIDTH
+    cur = shutil.get_terminal_size()[0]
+    if cur < width:
+        width = cur
+    return width
+
+try:
+    from PIL import Image
+    _HAS_PIL = True
+except ModuleNotFoundError:
+    _HAS_PIL = False
+_HAS_TIMG = shutil.which('timg')
+_HAS_CHAFA = shutil.which('chafa')
+_NEW_CHAFA = False
+_NEW_TIMG = False
+_RENDER_IMAGE = False
+
+# All this code to know if we render image inline or not
+if _HAS_CHAFA:
+    # starting with 1.10, chafa can return only one frame
+    # which allows us to drop dependancy for PIL
+    output = run("chafa --version")
+    # output is "Chafa version M.m.p"
+    # check for m < 1.10
+    try:
+        chafa_major, chafa_minor, _ = output.split("\n")[0].split(" ")[-1].split(".")
+        if int(chafa_major) >= 1 and int(chafa_minor) >= 10:
+            _NEW_CHAFA = True
+    except:
+        pass
+if _NEW_CHAFA :
+    _RENDER_IMAGE = True
+if _HAS_TIMG :
+    try:
+        output = run("timg --version")
+    except subprocess.CalledProcessError:
+        output = False
+    # We don’t deal with timg before 1.3.2 (looping options)
+    if output and output[5:10] > "1.3.2":
+        _NEW_TIMG = True
+        _RENDER_IMAGE = True
+elif _HAS_CHAFA and _HAS_PIL:
+    _RENDER_IMAGE = True
+if not _RENDER_IMAGE:
+    print("To render images inline, you need either chafa or timg.")
+    if not _NEW_CHAFA and not _NEW_TIMG:
+        print("Before Chafa 1.10, you also need python-pil")
+
+#return ANSI text that can be show by less
+def inline_image(img_file,width):
+    #Chafa is faster than timg inline. Let use that one by default
+    inline = None
+    ansi_img = ""
+    #We avoid errors by not trying to render non-image files
+    if shutil.which("file"):
+        mime = run("file -b --mime-type %s", parameter=img_file).strip()
+        if not "image" in mime:
+            return ansi_img
+    if _HAS_CHAFA:
+        if _HAS_PIL and not _NEW_CHAFA:
+            # this code is a hack to remove frames from animated gif
+            img_obj = Image.open(img_file)
+            if hasattr(img_obj,"n_frames") and img_obj.n_frames > 1:
+                # we remove all frames but the first one
+                img_obj.save(img_file,format="gif",save_all=False)
+            inline = "chafa --bg white -s %s -f symbols"
+        elif _NEW_CHAFA:
+            inline = "chafa --bg white -t 1 -s %s -f symbols --animate=off"
+    if not inline and _NEW_TIMG:
+        inline = "timg --frames=1 -p q -g %sx1000"
+    if inline:
+        cmd = inline%width + " %s"
+        try:
+            ansi_img = run(cmd, parameter=img_file)
+        except Exception as err:
+            ansi_img = "***image failed : %s***\n" %err
+    return ansi_img
+
+def terminal_image(img_file):
+    #Render by timg is better than old chafa.
+    # it is also centered
+    cmd = None
+    if _NEW_TIMG:
+        cmd = "timg --loops=1 -C"
+    elif _HAS_CHAFA:
+        cmd = "chafa -d 0 --bg white -t 1 -w 1"
+    if cmd:
+        cmd = cmd + " %s"
+        run(cmd, parameter=img_file, direct_output=True)
+
+def parse_mime(mime):
+    options = {}
+    if mime:
+        if ";" in mime:
+            splited = mime.split(";",maxsplit=1)
+            mime = splited[0]
+            if len(splited) >= 1:
+                options_list = splited[1].split()
+                for o in options_list:
+                    spl = o.split("=",maxsplit=1)
+                    if len(spl) > 0:
+                        options[spl[0]] = spl[1]
+    return mime, options
+
+_HAS_XSEL = shutil.which('xsel')
+_HAS_XDGOPEN = shutil.which('xdg-open')
+try:
+    from cryptography import x509
+    from cryptography.hazmat.backends import default_backend
+    _HAS_CRYPTOGRAPHY = True
+    _BACKEND = default_backend()
+except ModuleNotFoundError:
+    _HAS_CRYPTOGRAPHY = False
+
+try:
+    import requests
+    _DO_HTTP = True
+except ModuleNotFoundError:
+    _DO_HTTP = False
+
+try:
+    from readability import Document
+    _HAS_READABILITY = True
+except ModuleNotFoundError:
+    _HAS_READABILITY = False
+
+try:
+    from bs4 import BeautifulSoup
+    from bs4 import Comment
+    _HAS_SOUP = True
+except ModuleNotFoundError:
+    _HAS_SOUP = False
+
+_DO_HTML = _HAS_SOUP #and _HAS_READABILITY
+if _DO_HTML and not _HAS_READABILITY:
+    print("To improve your web experience (less cruft in webpages),")
+    print("please install python3-readability or readability-lxml")
+
+try:
+    import feedparser
+    _DO_FEED = True
+except ModuleNotFoundError:
+    _DO_FEED = False
+
+## Config directories
+## We implement our own python-xdg to avoid conflict with existing libraries.
+_home = os.path.expanduser('~')
+data_home = os.environ.get('XDG_DATA_HOME') or \
+            os.path.join(_home,'.local','share')
+config_home = os.environ.get('XDG_CONFIG_HOME') or \
+                os.path.join(_home,'.config')
+cache_home = os.environ.get('XDG_CACHE_HOME') or\
+                os.path.join(_home,'.cache')
+_CACHE_PATH = os.path.join(cache_home,"offpunk/")
+_CONFIG_DIR = os.path.join(config_home,"offpunk/")
+_DATA_DIR = os.path.join(data_home,"offpunk/")
+_old_config = os.path.expanduser("~/.offpunk/")
+## Look for pre-existing config directory, if any
+if os.path.exists(_old_config):
+    _CONFIG_DIR = _old_config
+#if no XDG .local/share and not XDG .config, we use the old config
+if not os.path.exists(data_home) and os.path.exists(_old_config):
+    _DATA_DIR = _CONFIG_DIR
+_MAX_REDIRECTS = 5
+_MAX_CACHE_SIZE = 10
+_MAX_CACHE_AGE_SECS = 180
+
+_GREP = "grep --color=auto"
+less_version = 0
+if not shutil.which("less"):
+    print("Please install the pager \"less\" to run Offpunk.")
+    print("If you wish to use another pager, send your request to offpunk@ploum.eu.")
+    print("(I’m really curious to hear about people not having \"less\" on their system.)")
+    sys.exit()
+output = run("less --version")
+# We get less Version (which is the only integer on the first line)
+words = output.split("\n")[0].split()
+less_version = 0
+for w in words:
+    if w.isdigit():
+        less_version = int(w)
+# restoring position only works for version of less > 572
+if less_version >= 572:
+    _LESS_RESTORE_POSITION = True
+else:
+    _LESS_RESTORE_POSITION = False
+#_DEFAULT_LESS = "less -EXFRfM -PMurl\ lines\ \%lt-\%lb/\%L\ \%Pb\%$ %s"
+# -E : quit when reaching end of file (to behave like "cat")
+# -F : quit if content fits the screen (behave like "cat")
+# -X : does not clear the screen
+# -R : interpret ANSI colors correctly
+# -f : suppress warning for some contents
+# -M : long prompt (to have info about where you are in the file)
+# -W : hilite the new first line after a page skip (space)
+# -i : ignore case in search
+# -S : do not wrap long lines. Wrapping is done by offpunk, longlines
+# are there on purpose (surch in asciiart)
+#--incsearch : incremental search starting rev581
+if less_version >= 581:
+    less_base = "less --incsearch --save-marks -~ -XRfMWiS"
+elif less_version >= 572:
+    less_base = "less --save-marks -XRfMWiS"
+else:
+    less_base = "less -XRfMWiS"
+_DEFAULT_LESS = less_base + " \"+''\" %s"
+_DEFAULT_CAT = less_base + " -EF %s"
+def less_cmd(file, histfile=None,cat=False,grep=None):
+    if histfile:
+        env = {"LESSHISTFILE": histfile}
+    else:
+        env = {}
+    if cat:
+        cmd_str = _DEFAULT_CAT
+    elif grep:
+        grep_cmd = _GREP
+        #case insensitive for lowercase search
+        if grep.islower():
+            grep_cmd += " -i"
+        cmd_str = _DEFAULT_CAT + "|" + grep_cmd + " %s"%grep
+    else:
+        cmd_str = _DEFAULT_LESS
+    run(cmd_str, parameter=file, direct_output=True, env=env)
+
+
+# Command abbreviations
+_ABBREVS = {
+    "a":    "add",
+    "b":    "back",
+    "bb":   "blackbox",
+    "bm":   "bookmarks",
+    "book": "bookmarks",
+    "cp":   "copy",
+    "f":   "forward",
+    "g":    "go",
+    "h":    "history",
+    "hist": "history",
+    "l":    "view",
+    "less": "view",
+    "man":  "help",
+    "mv":   "move",
+    "n":    "next",
+    "off":  "offline",
+    "on":   "online",
+    "p":    "previous",
+    "prev": "previous",
+    "q":    "quit",
+    "r":    "reload",
+    "s":    "save",
+    "se":   "search",
+    "/":    "find",
+    "t":    "tour",
+    "u":    "up",
+    "v":    "view",
+    "w":    "wikipedia",
+    "wen":  "wikipedia en",
+    "wfr":  "wikipedia fr",
+    "wes":  "wikipedia es",
+}
+
+_MIME_HANDLERS = {
+}
+
+# monkey-patch Gemini support in urllib.parse
+# see https://github.com/python/cpython/blob/master/Lib/urllib/parse.py
+urllib.parse.uses_relative.append("gemini")
+urllib.parse.uses_netloc.append("gemini")
+urllib.parse.uses_relative.append("spartan")
+urllib.parse.uses_netloc.append("spartan")
+urllib.parse.uses_relative.append("rrtp")
+urllib.parse.uses_netloc.append("rrtp")
+
+#An IPV6 URL should be put between []  
+#We try to detect them has location with more than 2 ":"
+def fix_ipv6_url(url):
+    if not url or url.startswith("mailto"):
+        return url
+    if "://" in url:
+        schema, schemaless = url.split("://",maxsplit=1)
+    else:
+        schema, schemaless = None, url
+    if "/" in schemaless:
+        netloc, rest = schemaless.split("/",1)
+        if netloc.count(":") > 2 and "[" not in netloc and "]" not in netloc:
+            schemaless = "[" + netloc + "]" + "/" + rest
+    elif schemaless.count(":") > 2:
+        schemaless = "[" + schemaless + "]/"
+    if schema:
+        return schema + "://" + schemaless
+    return schemaless
+
+# This list is also used as a list of supported protocols
+standard_ports = {
+        "gemini" : 1965,
+        "gopher" : 70,
+        "finger" : 79,
+        "http"   : 80,
+        "https"  : 443,
+        "spartan": 300,
+}
+
+# First, we define the different content->text renderers, outside of the rest
+# (They could later be factorized in other files or replaced)
+class AbstractRenderer():
+    def __init__(self,content,url,center=True):
+        self.url = url
+        self.body = content
+        #there’s one rendered text and one links table per mode
+        self.rendered_text = {}
+        self.links = {}
+        self.images = {}
+        self.title = None
+        self.validity = True
+        self.temp_file = {}
+        self.less_histfile = {}
+        self.center = center
+   
+    #This class hold an internal representation of the HTML text
+    class representation:
+        def __init__(self,width,title=None,center=True):
+            self.title=title
+            self.center = center
+            self.final_text = ""
+            self.opened = []
+            self.width = width
+            self.last_line = ""
+            self.last_line_colors = {}
+            self.last_line_center = False
+            self.new_paragraph = True
+            self.i_indent = ""
+            self.s_indent = ""
+            self.r_indent = ""
+            self.current_indent = ""
+            self.disabled_indents = None
+            # each color is an [open,close] pair code
+            self.colors = { 
+                            "bold"   : ["1","22"],
+                            "faint"  : ["2","22"],
+                            "italic" : ["3","23"],
+                            "underline": ["4","24"],
+                            "red"    : ["31","39"],
+                            "yellow" : ["33","39"],
+                            "blue"   : ["34","39"],
+                       }
+
+        def _insert(self,color,open=True):
+            if open: o = 0 
+            else: o = 1
+            pos = len(self.last_line)
+            #we remember the position where to insert color codes
+            if not pos in self.last_line_colors:
+                self.last_line_colors[pos] = []
+            #Two inverse code cancel each other
+            if [color,int(not o)] in self.last_line_colors[pos]:
+                self.last_line_colors[pos].remove([color,int(not o)])
+            else:
+                self.last_line_colors[pos].append([color,o])#+color+str(o))
+        
+        # Take self.last line and add ANSI codes to it before adding it to 
+        # self.final_text.
+        def _endline(self):
+            if len(self.last_line.strip()) > 0:
+                for c in self.opened:
+                    self._insert(c,open=False)
+                nextline = ""
+                added_char = 0
+                #we insert the color code at the saved positions
+                while len (self.last_line_colors) > 0:
+                    pos,colors = self.last_line_colors.popitem()
+                    #popitem itterates LIFO. 
+                    #So we go, backward, to the pos (starting at the end of last_line)
+                    nextline = self.last_line[pos:] + nextline
+                    ansicol = "\x1b["
+                    for c,o in colors:
+                        ansicol += self.colors[c][o] + ";"
+                    ansicol = ansicol[:-1]+"m"
+                    nextline = ansicol + nextline
+                    added_char += len(ansicol)
+                    self.last_line = self.last_line[:pos]
+                nextline = self.last_line + nextline
+                if self.last_line_center:
+                    #we have to care about the ansi char while centering
+                    width = term_width() + added_char
+                    nextline = nextline.strip().center(width)
+                    self.last_line_center = False
+                else:
+                    #should we lstrip the nextline in the addition ?
+                    nextline = self.current_indent + nextline.lstrip() + self.r_indent
+                    self.current_indent = self.s_indent
+                self.final_text += nextline
+                self.last_line = ""
+                self.final_text += "\n"
+                for c in self.opened:
+                    self._insert(c,open=True)
+            else:
+                self.last_line = ""
+
+        
+        def center_line(self):
+            self.last_line_center = True
+        
+        def open_color(self,color):
+            if color in self.colors and color not in self.opened:
+                self._insert(color,open=True)
+                self.opened.append(color)
+        def close_color(self,color):
+            if color in self.colors and color in self.opened:
+                self._insert(color,open=False)
+                self.opened.remove(color)
+        def close_all(self):
+            if len(self.colors) > 0:
+                self.last_line += "\x1b[0m"
+                self.opened.clear()
+
+        def startindent(self,indent,sub=None,reverse=None):
+            self._endline()
+            self.i_indent = indent
+            self.current_indent = indent
+            if sub:
+                self.s_indent = sub
+            else:
+                self.s_indent = indent
+            if reverse:
+                self.r_indent = reverse
+            else:
+                self.r_indent = ""
+
+
+        def endindent(self):
+            self._endline()
+            self.i_indent = ""
+            self.s_indent = ""
+            self.r_indent = ""
+            self.current_indent = ""
+
+        def _disable_indents(self):
+            self.disabled_indents = []
+            self.disabled_indents.append(self.current_indent)
+            self.disabled_indents.append(self.i_indent)
+            self.disabled_indents.append(self.s_indent)
+            self.disabled_indents.append(self.r_indent)
+            self.endindent()
+
+        def _enable_indents(self):
+            if self.disabled_indents:
+                self.current_indent = self.disabled_indents[0]
+                self.i_indent = self.disabled_indents[1]
+                self.s_indent = self.disabled_indents[2]
+                self.r_indent = self.disabled_indents[3]
+            self.disabled_indents = None
+
+        def newline(self):
+            self._endline()
+
+        #A new paragraph implies 2 newlines (1 blank line between paragraphs)
+        #But it is only used if didn’t already started one to avoid plenty 
+        #of blank lines. force=True allows to bypass that limit.
+        #new_paragraph becomes false as soon as text is entered into it
+        def newparagraph(self,force=False):
+            if force or not self.new_paragraph:
+                self._endline()
+                self.final_text += "\n"
+                self.new_paragraph = True
+
+        def add_space(self):
+            if len(self.last_line) > 0 and self.last_line[-1] != " ":
+                self.last_line += " "
+
+        def _title_first(self,intext=None):
+            if self.title:
+                if not self.title == intext:
+                    self._disable_indents()
+                    self.open_color("blue")
+                    self.open_color("bold")
+                    self.open_color("underline")
+                    self.add_text(self.title)
+                    self.close_all()
+                    self.newparagraph()
+                    self._enable_indents()
+                self.title = None
+
+        # Beware, blocks are not wrapped nor indented and left untouched!
+        # They are mostly useful for pictures and preformatted text.
+        def add_block(self,intext):
+            # If necessary, we add the title before a block
+            self._title_first()
+            # we don’t want to indent blocks
+            self._endline()
+            self._disable_indents()
+            self.final_text += self.current_indent + intext
+            self.new_paragraph = False
+            self._endline()
+            self._enable_indents()
+        
+        def add_text(self,intext):
+            self._title_first(intext=intext)
+            lines = []
+            last = (self.last_line + intext)
+            self.last_line = ""
+            # With the following, we basically cancel adding only spaces
+            # on an empty line
+            if len(last.strip()) > 0:
+                self.new_paragraph = False
+            else:
+                last = last.strip()
+            if len(last) > self.width:
+                width = self.width - len(self.current_indent) - len(self.r_indent)
+                spaces_left = len(last) - len(last.lstrip())
+                spaces_right = len(last) - len(last.rstrip())
+                lines = textwrap.wrap(last,width,drop_whitespace=True)
+                self.last_line += spaces_left*" "
+                while len(lines) > 1:
+                    l = lines.pop(0)
+                    self.last_line += l
+                    self._endline()
+                if len(lines) == 1:
+                    li = lines[0]
+                    self.last_line += li + spaces_right*" "
+            else:
+                self.last_line = last
+
+        def get_final(self):
+            self.close_all()
+            self._endline()
+            #if no content, we still add the title
+            self._title_first()
+            lines = self.final_text.splitlines()
+            lines2 = []
+            termspace = shutil.get_terminal_size()[0]
+            #Following code instert blanck spaces to center the content
+            if self.center and termspace > term_width():
+                margin = int((termspace - term_width())//2)
+            else:
+                margin = 0
+            for l in lines :
+                lines2.append(margin*" "+l)
+            return "\n".join(lines2)
+
+    def get_subscribe_links(self):
+        return [[self.url,self.get_mime(),self.get_title()]]
+    def is_valid(self):
+        return self.validity
+    def get_links(self,mode="links_only"):
+        if mode not in self.links :
+            prepared_body = self.prepare(self.body,mode=mode)
+            results = self.render(prepared_body,mode=mode)
+            if results:
+                self.links[mode] = results[1]
+                for l in self.get_subscribe_links()[1:]:
+                    self.links[mode].append(l[0])
+        return self.links[mode]
+    def get_title(self):
+        return "Abstract title"
+   
+    # This function return a list of URL which should be downloaded
+    # before displaying the page (images in HTML pages, typically)
+    def get_images(self,mode="readable"):
+        if not mode in self.images:
+            self.get_body(mode=mode)
+            # we also invalidate the body that was done without images
+            self.rendered_text.pop(mode)
+        if mode in self.images:
+            return self.images[mode]
+        else:
+            return []
+    #This function will give gemtext to the gemtext renderer
+    def prepare(self,body,mode=None):
+        return body
+    
+    def get_body(self,width=None,mode="readable"):
+        if not width:
+            width = term_width()
+        if mode not in self.rendered_text:
+            prepared_body = self.prepare(self.body,mode=mode)
+            result = self.render(prepared_body,width=width,mode=mode)
+            if result:
+                self.rendered_text[mode] = result[0]
+                self.links[mode] = result[1]
+        return self.rendered_text[mode]
+
+    def _window_title(self,title,info=None):
+        title_r = self.representation(term_width())
+        title_r.open_color("red")
+        title_r.open_color("bold")
+        title_r.add_text(title)
+        title_r.close_color("bold")
+        if info:
+            title_r.add_text("   (%s)"%info)
+        title_r.close_color("red")
+        return title_r.get_final() 
+
+    def display(self,mode="readable",window_title="",window_info=None,grep=None):
+        if not mode: mode = "readable"
+        wtitle = self._window_title(window_title,info=window_info)
+        body = wtitle + "\n" + self.get_body(mode=mode)
+        if not body:
+            return False
+        # We actually put the body in a tmpfile before giving it to less
+        if mode not in self.temp_file:
+            tmpf = tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False)
+            self.temp_file[mode] = tmpf.name
+            tmpf.write(body)
+            tmpf.close()
+        if mode not in self.less_histfile:
+            firsttime = True
+            tmpf = tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False)
+            self.less_histfile[mode] = tmpf.name
+        else:
+            firsttime = False
+        less_cmd(self.temp_file[mode], histfile=self.less_histfile[mode],cat=firsttime,grep=grep)
+        return True
+    
+    def get_temp_file(self,mode="readable"):
+        if mode in self.temp_file:
+            return self.temp_file[mode]
+        else:
+            return None
+
+    # An instance of AbstractRenderer should have a self.render(body,width,mode) method.
+    # 3 modes are used : readable (by default), full and links_only (the fastest, when
+    # rendered content is not used, only the links are needed)
+    # The prepare() function is called before the rendering. It is useful if
+    # your renderer output in a format suitable for another existing renderer (such as gemtext)
+
+# Gemtext Rendering Engine
+class GemtextRenderer(AbstractRenderer):
+    def get_mime(self):
+        return "text/gemini"
+    def get_title(self):
+        if self.title:
+            return self.title
+        elif self.body:
+            lines = self.body.splitlines()
+            for line in lines:
+                if line.startswith("#"):
+                    self.title = line.strip("#").strip()
+                    return self.title
+            if len(lines) > 0:
+                # If not title found, we take the first 50 char 
+                # of the first line
+                title_line = lines[0].strip()
+                if len(title_line) > 50:
+                    title_line = title_line[:49] + "…"
+                self.title = title_line
+                return self.title
+            else:
+                self.title = "Empty Page"
+                return self.title
+        else:
+            return "Unknown Gopher Page"
+    
+    #render_gemtext
+    def render(self,gemtext, width=None,mode=None):
+        if not width:
+            width = term_width()
+        r = self.representation(width)
+        links = []
+        hidden_links = []
+        preformatted = False
+        def format_link(url,index,name=None):
+            if "://" in url:
+                protocol,adress = url.split("://",maxsplit=1)
+                protocol = " %s" %protocol
+            else:
+                adress = url
+                protocol = ""
+            if "gemini" in protocol or "list" in protocol:
+                protocol = ""
+            if not name:
+                name = adress
+            line = "[%d%s] %s" % (index, protocol, name)
+            return line
+        for line in gemtext.splitlines():
+            r.newline()
+            if line.startswith("```"):
+                preformatted = not preformatted
+            elif preformatted:
+                # infinite line to not wrap preformated
+                r.add_block(line+"\n")
+            elif len(line.strip()) == 0:
+                r.newparagraph(force=True)
+            elif line.startswith("=>"):
+                strippedline = line[2:].strip()
+                if strippedline:
+                    links.append(strippedline)        
+                    splitted = strippedline.split(maxsplit=1)
+                    url = splitted[0]
+                    name = None
+                    if len(splitted) > 1:
+                        name = splitted[1]
+                    link = format_link(url,len(links),name=name)
+                    #r.open_color("blue")
+                    #r.open_color("faint")
+                    #r.open_color("underline")
+                    startpos = link.find("] ") + 2
+                    r.startindent("",sub=startpos*" ")
+                    r.add_text(link)
+                    r.endindent()
+                    #r.close_all()
+            elif line.startswith("* "):
+                line = line[1:].lstrip("\t ")
+                r.startindent("• ",sub="  ")
+                r.add_text(line)
+                r.endindent()
+            elif line.startswith(">"):
+                line = line[1:].lstrip("\t ")
+                r.startindent("> ")
+                r.add_text(line)
+                r.endindent()
+            elif line.startswith("###"):
+                line = line[3:].lstrip("\t ")
+                r.open_color("blue")
+                r.add_text(line)
+                r.close_color("blue")
+            elif line.startswith("##"):
+                line = line[2:].lstrip("\t ")
+                r.open_color("blue")
+                r.add_text(line)
+                r.close_color("blue")
+            elif line.startswith("#"):
+                line = line[1:].lstrip("\t ")
+                if not self.title:
+                    self.title = line
+                r.open_color("bold")
+                r.open_color("blue")
+                r.open_color("underline")
+                r.add_text(line)
+                r.close_color("underline")
+                r.close_color("bold")
+                r.close_color("blue")
+            else:
+                if "://" in line:
+                    words = line.split()
+                    for w in words:
+                        if "://" in w:
+                            hidden_links.append(w)
+                r.add_text(line.rstrip())
+        links += hidden_links
+        return r.get_final(), links
+
+class GopherRenderer(AbstractRenderer):
+    def get_mime(self):
+        return "text/gopher"
+    def get_title(self):
+        if not self.title:
+            self.title = ""
+            if self.body:
+                firstline = self.body.splitlines()[0]
+                firstline = firstline.split("\t")[0]
+                if firstline.startswith("i"):
+                    firstline = firstline[1:]
+                self.title = firstline
+        return self.title
+
+    #menu_or_text
+    def render(self,body,width=None,mode=None):
+        if not width:
+            width = term_width()
+        try:
+            render,links = self._render_goph(body,width=width,mode=mode)
+        except Exception as err:
+            print("Error rendering Gopher ",err)
+            r = self.representation(width)
+            r.add_block(body)
+            render = r.get_final()
+            links = []
+        return render,links
+
+    def _render_goph(self,body,width=None,mode=None):
+        if not width:
+            width = term_width()
+        # This was copied straight from Agena (then later adapted)
+        links = []
+        r = self.representation(width)
+        for line in self.body.split("\n"):
+            r.newline()
+            if line.startswith("i"):
+                towrap = line[1:].split("\t")[0]
+                if len(towrap.strip()) > 0:
+                    r.add_text(towrap)
+                else:
+                    r.newparagraph()
+            elif not line.strip() in [".",""]:
+                parts = line.split("\t")
+                parts[-1] = parts[-1].strip()
+                if parts[-1] == "+":
+                    parts = parts[:-1]
+                if len(parts) == 4:
+                    name,path,host,port = parts
+                    itemtype = name[0]
+                    name = name[1:]
+                    if port == "70":
+                        port = ""
+                    else:
+                        port = ":%s"%port
+                    if itemtype == "h" and path.startswith("URL:"):
+                        url = path[4:]
+                    else:
+                        if not path.startswith("/"):
+                            path = "/"+path
+                        url = "gopher://%s%s/%s%s" %(host,port,itemtype,path)
+                    url = url.replace(" ","%20")
+                    linkline = url + " " + name
+                    links.append(linkline)
+                    towrap = "[%s] "%len(links)+ name
+                    r.add_text(towrap)
+                else:
+                    r.add_text(line)
+        return r.get_final(),links
+
+
+class FolderRenderer(GemtextRenderer):
+    def get_mime(self):
+        return "Directory"
+    def prepare(self,body,mode=None):
+        def get_first_line(l):
+            path = os.path.join(listdir,l+".gmi")
+            with open(path) as f:
+                first_line = f.readline().strip()
+                f.close()
+            if first_line.startswith("#"):
+                return first_line
+            else:
+                return None
+        def write_list(l):
+            body = ""
+            for li in l:
+                path = "list:///%s"%li
+                gi = GeminiItem(path)
+                size = len(gi.get_links())
+                body += "=> %s %s (%s items)\n" %(str(path),li,size)
+            return body
+        listdir = os.path.join(_DATA_DIR,"lists")
+        if self.url != listdir:
+            return "This is folder %s" %self.url
+        else:
+            self.title = "My lists"
+            lists = []
+            if os.path.exists(listdir):
+                listfiles = os.listdir(listdir)
+                if len(listfiles) > 0:
+                    for l in listfiles:
+                        #removing the .gmi at the end of the name
+                        lists.append(l[:-4])
+            if len(lists) > 0:
+                body = ""
+                my_lists = []
+                system_lists = []
+                subscriptions = []
+                frozen = []
+                lists.sort()
+                for l in lists:
+                    if l in ["history","to_fetch","archives","tour"]:
+                        system_lists.append(l)
+                    else:
+                        first_line = get_first_line(l)
+                        if first_line and "#subscribed" in first_line:
+                            subscriptions.append(l)
+                        elif first_line and "#frozen" in first_line:
+                            frozen.append(l)
+                        else:
+                            my_lists.append(l)
+                if len(my_lists) > 0:
+                    body+= "\n## Bookmarks Lists (updated during sync)\n"
+                    body += write_list(my_lists)
+                if len(subscriptions) > 0:
+                    body +="\n## Subscriptions (new links in those are added to tour)\n"
+                    body += write_list(subscriptions)
+                if len(frozen) > 0:
+                    body +="\n## Frozen (fetched but never updated)\n"
+                    body += write_list(frozen)
+                if len(system_lists) > 0:
+                    body +="\n## System Lists\n"
+                    body += write_list(system_lists)
+                return body
+
+class FeedRenderer(GemtextRenderer):
+    def get_mime(self):
+        return "application/rss+xml"
+    def is_valid(self):
+        if _DO_FEED:
+            parsed = feedparser.parse(self.body)
+        else:
+            return False
+        if parsed.bozo:
+            return False
+        else:
+            #If no content, then fallback to HTML
+            return len(parsed.entries) > 0
+
+    def get_title(self):
+        if not self.title:
+            self.get_body()
+        return self.title
+
+    def prepare(self,content,mode="readable",width=None):
+        if not width:
+            width = term_width()
+        self.title = "RSS/Atom feed"
+        page = ""
+        if _DO_FEED:
+            parsed = feedparser.parse(content)
+        else:
+            page += "Please install python-feedparser to handle RSS/Atom feeds\n"
+            self.validity = False
+            return page
+        if parsed.bozo:
+            page += "Invalid RSS feed\n\n"
+            page += str(parsed.bozo_exception)
+            self.validity = False
+        else:
+            if "title" in parsed.feed:
+                t = parsed.feed.title
+            else:
+                t = "Unknown"
+            self.title = "%s (XML feed)" %t
+            title = "# %s"%self.title
+            page += title + "\n"
+            if "updated" in parsed.feed:
+                page += "Last updated on %s\n\n" %parsed.feed.updated
+            if "subtitle" in parsed.feed:
+                page += parsed.feed.subtitle + "\n"
+            if "link" in parsed.feed:
+                page += "=> %s\n" %parsed.feed.link
+            page += "\n## Entries\n"
+            if len(parsed.entries) < 1:
+                self.validity = False
+            for i in parsed.entries:
+                line = "=> %s " %i.link
+                if "published" in i:
+                    pub_date = time.strftime("%Y-%m-%d",i.published_parsed)
+                    line += pub_date + " : "
+                line += "%s" %(i.title)
+                if "author" in i:
+                    line += " (by %s)"%i.author
+                page += line + "\n"
+                if mode == "full":
+                    if "summary" in i:
+                        html = HtmlRenderer(i.summary,self.url,center=False)
+                        rendered = html.get_body(width=None,mode="full")
+                        page += rendered
+                        page += "\n"
+        return page
+
+class ImageRenderer(AbstractRenderer):
+    def get_mime(self):
+        return "image/*"
+    def is_valid(self):
+        if _RENDER_IMAGE:
+            return True
+        else:
+            return False
+    def get_links(self,mode=None):
+        return []
+    def get_title(self):
+        return "Picture file"
+    def render(self,img,width=None,mode=None):
+        #with inline, we use symbols to be rendered with less.
+        #else we use the best possible renderer.
+        if mode == "links_only":
+            return "", []
+        if not width:
+            width = term_width()
+            spaces = 0
+        else:
+            spaces = int((term_width() - width)//2)
+        ansi_img = inline_image(img,width)
+        #Now centering the image
+        lines = ansi_img.splitlines()
+        new_img = ""
+        for l in lines:
+            new_img += spaces*" " + l + "\n"
+        return new_img, []
+    def display(self,mode=None,window_title=None,window_info=None,grep=None):
+        if window_title:
+            print(self._window_title(window_title,info=window_info))
+        terminal_image(self.body)
+        return True
+
+class HtmlRenderer(AbstractRenderer):
+    def get_mime(self):
+        return "text/html"
+    def is_valid(self):
+        if not _DO_HTML:
+            print("HTML document detected. Please install python-bs4 and python-readability.")
+        return _DO_HTML and self.validity
+    def get_subscribe_links(self):
+        subs = [[self.url,self.get_mime(),self.get_title()]]
+        soup = BeautifulSoup(self.body, 'html.parser')
+        links = soup.find_all("link",rel="alternate",recursive=True)
+        for l in links:
+            ty = l.get("type")
+            if ty :
+                if "rss" in ty or "atom" in ty or "feed" in ty:
+                    subs.append([l.get("href"),ty,l.get("title")])
+        return subs
+
+    def get_title(self):
+        if self.title:
+            return self.title
+        elif self.body:
+            if _HAS_READABILITY:
+                try:
+                    readable = Document(self.body)
+                    self.title = readable.short_title()
+                    return self.title
+                except Exception as err:
+                    pass
+            soup = BeautifulSoup(self.body,"html.parser")
+            self.title = str(soup.title.string)
+        else:
+            return ""
+    
+    # Our own HTML engine (crazy, isn’t it?)
+    # Return [rendered_body, list_of_links]
+    # mode is either links_only, readable or full
+    def render(self,body,mode="readable",width=None,add_title=True):
+        if not width:
+            width = term_width()
+        if not _DO_HTML:
+            print("HTML document detected. Please install python-bs4 and python-readability.")
+            return
+        # This method recursively parse the HTML
+        r = self.representation(width,title=self.get_title(),center=self.center)
+        links = []
+        # You know how bad html is when you realize that space sometimes meaningful, somtimes not.
+        # CR are not meaniningful. Except that, somethimes, they should be interpreted as spaces.
+        # HTML is real crap. At least the one people are generating.
+
+        def render_image(src,width=40,mode=None):
+            ansi_img = ""
+            imgurl,imgdata = looks_like_base64(src,self.url)
+            if _RENDER_IMAGE and mode != "links_only" and imgurl:
+                try:
+                    #4 followings line are there to translate the URL into cache path
+                    g = GeminiItem(imgurl)
+                    img = g.get_cache_path()
+                    if imgdata:
+                        with open(img,"wb") as cached:
+                            cached.write(base64.b64decode(imgdata))
+                            cached.close()
+                    if g.is_cache_valid():
+                        renderer = ImageRenderer(img,imgurl)
+                        # Image are 40px wide except if terminal is smaller
+                        if width > 40:
+                            size = 40
+                        else:
+                            size = width
+                        ansi_img = "\n" + renderer.get_body(width=size,mode="inline")
+                except Exception as err:
+                    #we sometimes encounter really bad formatted files or URL
+                    ansi_img = textwrap.fill("[BAD IMG] %s - %s"%(err,src),width) + "\n"
+            return ansi_img
+        def sanitize_string(string):
+            #never start with a "\n"
+            #string = string.lstrip("\n")
+            string = string.replace("\r","").replace("\n", " ").replace("\t"," ")
+            endspace = string.endswith(" ") or string.endswith("\xa0")
+            startspace = string.startswith(" ") or string.startswith("\xa0")
+            toreturn = string.replace("\n", " ").replace("\t"," ").strip()
+            while "  " in toreturn:
+                toreturn = toreturn.replace("  "," ")
+            toreturn = html.unescape(toreturn)
+            if endspace and not toreturn.endswith(" ") and not toreturn.endswith("\xa0"):
+                toreturn += " "
+            if startspace and not toreturn.startswith(" ") and not toreturn.startswith("\xa0"):
+                toreturn = " " + toreturn
+            return toreturn
+        def recursive_render(element,indent="",preformatted=False):
+            if element.name == "blockquote":
+                r.newparagraph()
+                r.startindent("   ",reverse="     ")
+                for child in element.children:
+                    r.open_color("italic")
+                    recursive_render(child,indent="\t")
+                    r.close_color("italic")
+                r.endindent()
+            elif element.name in ["div","p"]:
+                r.newparagraph()
+                for child in element.children:
+                    recursive_render(child,indent=indent)
+                r.newparagraph()
+            elif element.name in ["span"]:
+                r.add_space()
+                for child in element.children:
+                    recursive_render(child,indent=indent)
+                r.add_space()
+            elif element.name in ["h1","h2","h3","h4","h5","h6"]:
+                r.open_color("blue")
+                if element.name in ["h1"]:
+                    r.open_color("bold")
+                    r.open_color("underline")
+                elif element.name in ["h2"]:
+                    r.open_color("bold")
+                elif element.name in ["h5","h6"]:
+                    r.open_color("faint")
+                for child in element.children:
+                    r.newparagraph()
+                    recursive_render(child)
+                    r.newparagraph()
+                    r.close_all()
+            elif element.name in ["code","tt"]:
+                for child in element.children:
+                   recursive_render(child,indent=indent,preformatted=True)
+            elif element.name in ["pre"]:
+                r.newparagraph()
+                r.add_block(element.text)
+                r.newparagraph()
+            elif element.name in ["li"]:
+                r.startindent(" • ",sub="   ")
+                for child in element.children:
+                    recursive_render(child,indent=indent)
+                r.endindent()
+            elif element.name in ["tr"]:
+                r.startindent("|",reverse="|")
+                for child in element.children:
+                    recursive_render(child,indent=indent)
+                r.endindent()
+            elif element.name in ["td","th"]:
+                r.add_text("| ")
+                for child in element.children:
+                    recursive_render(child)
+                r.add_text(" |")
+            # italics
+            elif element.name in ["em","i"]:
+                r.open_color("italic")
+                for child in element.children:
+                    recursive_render(child,indent=indent,preformatted=preformatted)
+                r.close_color("italic")
+            #bold
+            elif element.name in ["b","strong"]:
+                r.open_color("bold")
+                for child in element.children:
+                    recursive_render(child,indent=indent,preformatted=preformatted)
+                r.close_color("bold")
+            elif element.name == "a":
+                link = element.get('href')
+                # support for images nested in links
+                if link:
+                    text = ""
+                    imgtext = ""
+                    #we display images first in a link 
+                    for child in element.children:
+                        if child.name == "img":
+                            recursive_render(child)
+                            imgtext = "[IMG LINK %s]"
+                    links.append(link+" "+text)
+                    link_id = str(len(links))
+                    r.open_color("blue")
+                    r.open_color("faint")
+                    for child in element.children:
+                        if child.name != "img":
+                            recursive_render(child,preformatted=preformatted)
+                    if imgtext != "":
+                        r.center_line()
+                        r.add_text(imgtext%link_id)
+                    else:
+                        r.add_text(" [%s]"%link_id)
+                    r.close_color("blue")
+                    r.close_color("faint")
+                else:
+                    #No real link found
+                    for child in element.children:
+                        recursive_render(child,preformatted=preformatted)
+            elif element.name == "img":
+                src = element.get("src")
+                text = ""
+                ansi_img = render_image(src,width=width,mode=mode)
+                alt = element.get("alt")
+                if alt:
+                    alt = sanitize_string(alt)
+                    text += "[IMG] %s"%alt
+                else:
+                    text += "[IMG]"
+                if src:
+                    links.append(src+" "+text)
+                    if not mode in self.images:
+                        self.images[mode] = []
+                    abs_url = urllib.parse.urljoin(self.url, src)
+                    self.images[mode].append(abs_url)
+                    link_id = " [%s]"%(len(links))
+                    r.add_block(ansi_img)
+                    r.open_color("faint")
+                    r.open_color("yellow")
+                    r.center_line()
+                    r.add_text(text + link_id)
+                    r.close_color("faint")
+                    r.close_color("yellow")
+                    r.newline()
+            elif element.name == "br":
+                r.newline()
+            elif element.name not in ["script","style","template"] and type(element) != Comment:
+                if element.string:
+                    if preformatted :
+                        r.open_color("faint")
+                        r.add_text(element.string)
+                        r.close_color("faint")
+                    else:
+                        s = sanitize_string(element.string)
+                        if len(s.strip()) > 0:
+                            r.add_text(s)
+                else:
+                    for child in element.children:
+                        recursive_render(child,indent=indent)
+        # the real render_html hearth
+        if mode == "full":
+            summary = body
+        elif _HAS_READABILITY:
+            try:
+                readable = Document(body)
+                summary = readable.summary()
+            except Exception as err:
+                summary = body
+        else:
+            summary = body
+        soup = BeautifulSoup(summary, 'html.parser')
+        #soup = BeautifulSoup(summary, 'html5lib')
+        if soup :
+            if soup.body :
+                recursive_render(soup.body)
+            else:
+                recursive_render(soup)
+        return r.get_final(),links
+
+# Mapping mimetypes with renderers
+# (any content with a mimetype text/* not listed here will be rendered with as GemText)
+_FORMAT_RENDERERS = {
+    "text/gemini":  GemtextRenderer,
+    "text/html" :   HtmlRenderer,
+    "text/xml" : FeedRenderer,
+    "application/xml" : FeedRenderer,
+    "application/rss+xml" : FeedRenderer,
+    "application/atom+xml" : FeedRenderer,
+    "text/gopher": GopherRenderer,
+    "image/*": ImageRenderer
+}
+# Offpunk is organized as follow:
+# - a GeminiClient instance which handles the browsing of GeminiItems (= pages).
+# - There’s only one GeminiClient. Each page is a GeminiItem (name is historical, as
+# it could be non-gemini content)
+# - A GeminiItem is created with an URL from which it will derives content.
+# - Content include : a title, a body (raw source) and a renderer. The renderer will provide
+#                     ANSI rendered version of the content and a list of links
+# - Each GeminiItem generates a "cache_path" in which it maintains a cached version of its content.
+
+class GeminiItem():
+
+    def __init__(self, url, name=""):
+        if "://" not in url and ("./" not in url and url[0] != "/"):
+            if not url.startswith("mailto:"):
+                url = "gemini://" + url
+        self.last_mode = None
+        findmode = url.split("##offpunk_mode=")
+        if len(findmode) > 1:
+            self.url = findmode[0]
+            if findmode[1] in ["full"] or findmode[1].isnumeric():
+                self.last_mode = findmode[1]
+        else:
+            self.url = url
+        self.url = fix_ipv6_url(self.url).strip()
+        self._cache_path = None
+        self.name = name
+        self.mime = None
+        self.renderer = None
+        self.body = None
+        parsed = urllib.parse.urlparse(self.url)
+        if url[0] == "/" or url.startswith("./"):
+            self.scheme = "file"
+        else:
+            self.scheme = parsed.scheme
+        if self.scheme in ["file","mailto","list"]:
+            self.local = True
+            self.host = ""
+            self.port = None
+            # file:// is 7 char
+            if self.url.startswith("file://"):
+                self.path = self.url[7:]
+            elif self.scheme == "mailto":
+                self.path = parsed.path
+            elif self.url.startswith("list://"):
+                listdir = os.path.join(_DATA_DIR,"lists")
+                listname = self.url[7:].lstrip("/")
+                if listname in [""]:
+                    self.name = "My Lists"
+                    self.path = listdir
+                else:
+                    self.name = listname
+                    self.path = os.path.join(listdir, "%s.gmi"%listname)
+            else:
+                self.path = self.url
+        else:
+            self.local = False
+            # Convert unicode hostname to punycode using idna RFC3490
+            self.host = parsed.hostname #.encode("idna").decode()
+            self.port = parsed.port or standard_ports.get(self.scheme, 0)
+            # special gopher selector case
+            if self.scheme == "gopher":
+                if parsed.path and parsed.path[0] == "/" and len(parsed.path) > 1:
+                    splitted = parsed.path.split("/")
+                    # We check if we have well a gopher type
+                    if len(splitted[1]) == 1:
+                        itemtype = parsed.path[1]
+                        selector = parsed.path[2:]
+                    else:
+                        itemtype = "1"
+                        selector = parsed.path
+                    self.path = selector
+                else:
+                    itemtype = "1"
+                    self.path = parsed.path
+                if itemtype == "0":
+                    self.mime = "text/gemini"
+                elif itemtype == "1":
+                    self.mime = "text/gopher"
+                elif itemtype == "h":
+                    self.mime = "text/html"
+                elif itemtype in ("9","g","I","s"):
+                    self.mime = "binary"
+                else:
+                    self.mime = "text/gopher"
+            else:
+                self.path = parsed.path
+            if parsed.query:
+                # we don’t add the query if path is too long because path above 260 char
+                # are not supported and crash python.
+                # Also, very long query are usually useless stuff
+                if len(self.path+parsed.query) < 258:
+                    self.path += "/" + parsed.query
+    
+    def get_cache_path(self):
+        if self._cache_path and not os.path.isdir(self._cache_path):
+            return self._cache_path
+        elif self.local:
+            self._cache_path = self.path
+        #if not local, we create a local cache path.
+        else:
+            self._cache_path = os.path.expanduser(_CACHE_PATH + self.scheme +\
+                                                "/" + self.host + self.path)
+            #There’s an OS limitation of 260 characters per path. 
+            #We will thus cut the path enough to add the index afterward
+            self._cache_path = self._cache_path[:249]
+            # FIXME : this is a gross hack to give a name to
+            # index files. This will break if the index is not
+            # index.gmi. I don’t know how to know the real name
+            # of the file. But first, we need to ensure that the domain name
+            # finish by "/". Else, the cache will create a file, not a folder.
+            if self.scheme.startswith("http"):
+                index = "index.html"
+            elif self.scheme in ["gopher", "finger"]:
+                index = "index.txt"
+            else:
+                index = "index.gmi"
+            if self.path == "" or os.path.isdir(self._cache_path):
+                if not self._cache_path.endswith("/"):
+                    self._cache_path += "/"
+                if not self.url.endswith("/"):
+                    self.url += "/"
+            if self._cache_path.endswith("/"):
+                self._cache_path += index
+            #sometimes, the index itself is a dir
+            #like when folder/index.gmi?param has been created
+            #and we try to access folder
+            if os.path.isdir(self._cache_path):
+                self._cache_path += "/" + index
+        return self._cache_path
+            
+    def get_capsule_title(self):
+            #small intelligence to try to find a good name for a capsule
+            #we try to find eithe ~username or /users/username
+            #else we fallback to hostname
+            if self.local:
+                if self.name != "":
+                    red_title = self.name
+                else:
+                    red_title = self.path
+            else:
+                red_title = self.host
+                if "user" in self.path:
+                    i = 0
+                    splitted = self.path.split("/")
+                    while i < (len(splitted)-1):
+                        if splitted[i].startswith("user"):
+                            red_title = splitted[i+1]
+                        i += 1
+                if "~" in self.path:
+                    for pp in self.path.split("/"):
+                        if pp.startswith("~"):
+                            red_title = pp[1:]
+            return red_title
+   
+    def get_page_title(self):
+        title = ""
+        if not self.renderer:
+            self._set_renderer()
+        if self.renderer:
+            title = self.renderer.get_title()
+        if not title or len(title) == 0:
+            title = self.get_capsule_title()
+        else:
+            title += " (%s)" %self.get_capsule_title()
+        return title
+
+    def is_cache_valid(self,validity=0):
+        # Validity is the acceptable time for 
+        # a cache to be valid  (in seconds)
+        # If 0, then any cache is considered as valid
+        # (use validity = 1 if you want to refresh everything)
+        cache = self.get_cache_path()
+        if self.local:
+            return os.path.exists(cache)
+        elif cache :
+            # If path is too long, we always return True to avoid
+            # fetching it.
+            if len(cache) > 259:
+                print("We return False because path is too long")
+                return False
+            if os.path.exists(cache) and not os.path.isdir(cache):
+                if validity > 0 :
+                    last_modification = self.cache_last_modified()
+                    now = time.time()
+                    age = now - last_modification
+                    return age < validity
+                else:
+                    return True
+            else:
+                #Cache has not been build
+                return False
+        else:
+            #There’s not even a cache!
+            return False
+
+    def cache_last_modified(self):
+        path = self.get_cache_path()
+        if path:
+            return os.path.getmtime(path)
+        elif self.local:
+            return 0
+        else:
+            print("ERROR : NO CACHE in cache_last_modified")
+            return None
+    
+    def get_body(self,as_file=False):
+        if self.body and not as_file:
+            return self.body
+        if self.is_cache_valid():
+            path = self.get_cache_path()
+        else:
+            path = None
+        if path:
+            # There’s on OS limit on path length
+            if len(path) > 259:
+                toreturn = "Path is too long. This is an OS limitation.\n\n"
+                toreturn += self.url
+                return toreturn
+            elif as_file:
+                return path
+            else:
+                with open(path) as f:
+                    body = f.read()
+                    f.close()
+                return body
+        else:
+            #print("ERROR: NO CACHE for %s" %self._cache_path)
+            return None
+   
+    def get_images(self,mode=None):
+        if not self.renderer:
+            self._set_renderer()
+        if self.renderer:
+            return self.renderer.get_images(mode=mode)
+        else:
+            return []
+
+    # This method is used to load once the list of links in a gi
+    # Links can be followed, after a space, by a description/title
+    def get_links(self,mode=None):
+        links = []
+        toreturn = []
+        if not self.renderer:
+            self._set_renderer()
+        if self.renderer:
+            if not mode:
+                mode = self.last_mode
+            links = self.renderer.get_links(mode=mode)
+        for l in links:
+            #split between link and potential name
+            # check that l is non-empty
+            url = None
+            if l:   
+                splitted = l.split(maxsplit=1)
+                url = self.absolutise_url(splitted[0])
+            if url and looks_like_url(url):
+                if len(splitted) > 1:
+                    #We add a name only for Gopher items
+                    if url.startswith("gopher://"):
+                        newgi = GeminiItem(url,name=splitted[1])
+                    else:
+                        newgi = GeminiItem(url)
+                else:
+                    newgi = GeminiItem(url)
+                toreturn.append(newgi)
+            elif url and mode != "links_only" and url.startswith("data:image/"): 
+                imgurl,imgdata = looks_like_base64(url,self.url)
+                if imgurl:
+                    toreturn.append(GeminiItem(imgurl))
+                else:
+                    toreturn.append(None)
+            else:
+                # We must include a None item to keep the link count valid
+                toreturn.append(None)
+        return toreturn
+
+    def get_link(self,nb):
+        # == None allows to return False, even if the list is empty
+        links = self.get_links()
+        if len(links) < nb:
+            print("Index too high! No link %s for %s" %(nb,self.url))
+            return None
+        else:
+            return links[nb-1]
+
+    def get_subscribe_links(self):
+        if not self.renderer:
+            self._set_renderer()
+        if self.renderer:
+            subs = self.renderer.get_subscribe_links()
+            abssubs = []
+            # some rss links are relatives
+            for s in subs:
+                s[0] = self.absolutise_url(s[0])
+                abssubs.append(s)
+            return abssubs
+        else:
+            return []
+
+    def _set_renderer(self,mime=None):
+        if self.local and os.path.isdir(self.get_cache_path()):
+            self.renderer = FolderRenderer("",self.get_cache_path())
+            return
+        if not mime:
+            mime = self.get_mime()
+            #we don’t even have a mime (so probably we don’t have a cache)
+            if not mime:
+                return
+        mime_to_use = []
+        for m in _FORMAT_RENDERERS:
+            if fnmatch.fnmatch(mime, m):
+                mime_to_use.append(m)
+        if len(mime_to_use) > 0:
+            current_mime = mime_to_use[0]
+            func = _FORMAT_RENDERERS[current_mime]
+            if current_mime.startswith("text"):
+                self.renderer = func(self.get_body(),self.url)
+                # We double check if the renderer is correct.
+                # If not, we fallback to html
+                # (this is currently only for XHTML, often being
+                # mislabelled as xml thus RSS feeds)
+                if not self.renderer.is_valid():
+                    func = _FORMAT_RENDERERS["text/html"]
+                    #print("Set (fallback)RENDERER to html instead of %s"%mime)
+                    self.renderer = func(self.get_body(),self.url)
+            else:
+                #we don’t parse text, we give the file to the renderer
+                self.renderer = func(self.get_cache_path(),self.url)
+                if not self.renderer.is_valid():
+                    self.renderer = None
+
+    def display(self,mode=None,grep=None):
+        if not self.renderer:
+            self._set_renderer()
+        if self.renderer and self.renderer.is_valid():
+            if not mode:
+                mode = self.last_mode
+            else:
+                self.last_mode = mode
+            title = self.get_capsule_title()
+            if self.is_cache_valid(): #and self.offline_only and not self.local:
+                nbr = len(self.get_links(mode=mode))
+                if self.local:
+                    title += " (%s items)"%nbr
+                    str_last = "local file"
+                else:
+                    str_last = "last accessed on %s" %time.ctime(self.cache_last_modified())
+                    title += " (%s links)"%nbr
+                return self.renderer.display(mode=mode,window_title=title,window_info=str_last,grep=grep)
+            else:
+                return False
+        else:
+            return False
+
+    def get_filename(self):
+        filename = os.path.basename(self.get_cache_path())
+        return filename
+
+    def get_temp_filename(self):
+        tmpf = None
+        if not self.renderer:
+            self._set_renderer()
+        if self.renderer and self.renderer.is_valid():
+            tmpf = self.renderer.get_temp_file()
+        if not tmpf:
+            tmpf = self.get_cache_path()
+        return tmpf
+
+    def write_body(self,body,mime=None):
+        ## body is a copy of the raw gemtext
+        ## Write_body() also create the cache !
+        # DEFAULT GEMINI MIME
+        self.body = body
+        self.mime, options = parse_mime(mime)
+        if not self.local:
+            if self.mime and self.mime.startswith("text/"):
+                mode = "w"
+            else:
+                mode = "wb"
+            cache_dir = os.path.dirname(self.get_cache_path())
+            # If the subdirectory already exists as a file (not a folder)
+            # We remove it (happens when accessing URL/subfolder before
+            # URL/subfolder/file.gmi.
+            # This causes loss of data in the cache
+            # proper solution would be to save "sufolder" as "sufolder/index.gmi"
+            # If the subdirectory doesn’t exist, we recursively try to find one
+            # until it exists to avoid a file blocking the creation of folders
+            root_dir = cache_dir
+            while not os.path.exists(root_dir):
+                root_dir = os.path.dirname(root_dir)
+            if os.path.isfile(root_dir):
+                os.remove(root_dir)
+            os.makedirs(cache_dir,exist_ok=True)
+            with open(self.get_cache_path(), mode=mode) as f:
+                f.write(body)
+                f.close()
+         
+    def get_mime(self):
+        #Beware, this one is really a shaddy ad-hoc function
+        if self.mime:
+            return self.mime
+        elif self.is_cache_valid():
+            path = self.get_cache_path()
+            if self.scheme == "mailto":
+                mime = "mailto"
+            elif os.path.isdir(path):
+                mime = "Local Folder"
+            elif path.endswith(".gmi"):
+                mime = "text/gemini"
+            elif shutil.which("file") :
+                mime = run("file -b --mime-type %s", parameter=path).strip()
+                mime2,encoding = mimetypes.guess_type(path,strict=False)
+                #If we hesitate between html and xml, takes the xml one
+                #because the FeedRendered fallback to HtmlRenderer
+                if mime2 and mime != mime2 and "html" in mime and "xml" in mime2:
+                    mime = "text/xml"
+                # If it’s a xml file, consider it as such, regardless of what file thinks
+                elif path.endswith(".xml"):
+                    mime = "text/xml"
+                #Some xml/html document are considered as octet-stream
+                if mime == "application/octet-stream":
+                    mime = "text/xml"
+            else:
+                mime,encoding = mimetypes.guess_type(path,strict=False)
+            #gmi Mimetype is not recognized yet
+            if not mime and not shutil.which("file") :
+                print("Cannot guess the mime type of the file. Please install \"file\".")
+                print("(and send me an email, I’m curious of systems without \"file\" installed!")
+            if mime.startswith("text") and mime not in _FORMAT_RENDERERS:
+                if mime2 and mime2 in _FORMAT_RENDERERS:
+                    mime = mime2
+                else:
+                    #by default, we consider it’s gemini except for html
+                    mime = "text/gemini"
+            self.mime = mime
+        return self.mime
+    
+    def set_error(self,err):
+    # If we get an error, we want to keep an existing cache
+    # but we need to touch it or to create an empty one
+    # to avoid hitting the error at each refresh
+        cache = self.get_cache_path()
+        if self.is_cache_valid():
+            os.utime(cache)
+        else:
+            cache_dir = os.path.dirname(cache)
+            root_dir = cache_dir
+            while not os.path.exists(root_dir):
+                root_dir = os.path.dirname(root_dir)
+            if os.path.isfile(root_dir):
+                os.remove(root_dir)
+            os.makedirs(cache_dir,exist_ok=True)
+            if os.path.isdir(cache_dir):
+                with open(cache, "w") as cache:
+                    cache.write(str(datetime.datetime.now())+"\n")
+                    cache.write("ERROR while caching %s\n\n" %self.url)
+                    cache.write("*****\n\n")
+                    cache.write(str(type(err)) + " = " + str(err))
+                    #cache.write("\n" + str(err.with_traceback(None)))
+                    cache.write("\n*****\n\n")
+                    cache.write("If you believe this error was temporary, type ""reload"".\n")
+                    cache.write("The ressource will be tentatively fetched during next sync.\n")
+                    cache.close()
+    
+               
+    def root(self):
+        return GeminiItem(self._derive_url("/"))
+
+    def up(self,level=1):
+        path = self.path.rstrip('/')
+        count = 0
+        while count < level:
+            pathbits = list(os.path.split(path))
+            # Don't try to go higher than root or in config
+            if self.local or len(pathbits) == 1 :
+                return self
+            # Get rid of bottom component
+            if len(pathbits) > 1:
+                pathbits.pop()
+            path = os.path.join(*pathbits)
+            count += 1
+        if self.scheme == "gopher":
+            path = "/1" + path
+        return GeminiItem(self._derive_url(path))
+
+    def query(self, query):
+        query = urllib.parse.quote(query)
+        return GeminiItem(self._derive_url(query=query))
+
+    def _derive_url(self, path="", query=""):
+        """
+        A thin wrapper around urlunparse which avoids inserting standard ports
+        into URLs just to keep things clean.
+        """
+        if not self.port or self.port == standard_ports[self.scheme] :
+            host = self.host
+        else:
+            host = self.host + ":" + str(self.port)
+        return urllib.parse.urlunparse((self.scheme,host,path or self.path, "", query, ""))
+
+    def absolutise_url(self, relative_url):
+        """
+        Convert a relative URL to an absolute URL by using the URL of this
+        GeminiItem as a base.
+        """
+        abs_url = urllib.parse.urljoin(self.url, relative_url)
+        return abs_url
+
+    def url_mode(self):
+        url = self.url 
+        if self.last_mode and self.last_mode != "readable":
+            url += "##offpunk_mode=" + self.last_mode
+        return url
+
+    def to_map_line(self):
+        return "=> {} {}\n".format(self.url_mode(), self.get_page_title())
+
+CRLF = '\r\n'
+
+# Cheap and cheerful URL detector
+def looks_like_url(word):
+    print(word)
+    try:
+        if not word.strip():
+            return False
+        url = fix_ipv6_url(word).strip()
+        parsed = urllib.parse.urlparse(url)
+        #sometimes, urllib crashed only when requesting the port
+        port = parsed.port
+        mailto = word.startswith("mailto:")
+        scheme = word.split("://")[0]
+        start = scheme in standard_ports
+        local = scheme in ["file","list"]
+        rrtp = scheme == "rrtp"
+        if not start and not local and not mailto and not rrtp:
+            return looks_like_url("gemini://"+word)
+        elif rrtp:
+            return True
+        elif mailto:
+            return "@" in word
+        elif not local:
+            return "." in word or "localhost" in word
+        else:
+            return "/" in word
+    except ValueError:
+        return False
+
+# This method return the image URL or invent it if it’s a base64 inline image
+# It returns [url,image_data] where image_data is None for normal image
+def looks_like_base64(src,baseurl):
+    imgdata = None
+    imgname = src
+    if src and src.startswith("data:image/"):
+        if ";base64," in src:
+            splitted = src.split(";base64,")
+            extension = splitted[0].strip("data:image/")[:3]
+            imgdata = splitted[1]
+            imgname = imgdata[:20] + "." + extension
+            imgurl = urllib.parse.urljoin(baseurl, imgname)
+        else:
+            #We can’t handle other data:image such as svg for now
+            imgurl = None
+    else:
+        imgurl = urllib.parse.urljoin(baseurl, imgname)
+    return imgurl,imgdata
+
+class UserAbortException(Exception):
+    pass
+
+# GeminiClient Decorators
+def needs_gi(inner):
+    def outer(self, *args, **kwargs):
+        if not self.gi:
+            print("You need to 'go' somewhere, first")
+            return None
+        else:
+            return inner(self, *args, **kwargs)
+    outer.__doc__ = inner.__doc__
+    return outer
+
+class GeminiClient(cmd.Cmd):
+
+    def __init__(self, completekey="tab", synconly=False):
+        cmd.Cmd.__init__(self)
+
+        # Set umask so that nothing we create can be read by anybody else.
+        # The certificate cache and TOFU database contain "browser history"
+        # type sensitivie information.
+        os.umask(0o077)
+
+
+        self.no_cert_prompt = "\001\x1b[38;5;76m\002" + "ON" + "\001\x1b[38;5;255m\002" + "> " + "\001\x1b[0m\002"
+        self.cert_prompt = "\001\x1b[38;5;202m\002" + "ON" + "\001\x1b[38;5;255m\002"
+        self.offline_prompt = "\001\x1b[38;5;76m\002" + "OFF" + "\001\x1b[38;5;255m\002" + "> " + "\001\x1b[0m\001"
+        self.prompt = self.no_cert_prompt
+        self.gi = None
+        self.hist_index = 0
+        self.index = []
+        self.index_index = -1
+        self.marks = {}
+        self.page_index = 0
+        self.permanent_redirects = {}
+        self.previous_redirectors = set()
+        # Sync-only mode is restriced by design
+        self.visited_hosts = set()
+        self.offline_only = False
+        self.sync_only = False
+        self.support_http = _DO_HTTP
+        self.automatic_choice = "n"
+
+        self.client_certs = {
+            "active": None
+        }
+        self.active_cert_domains = []
+        self.active_is_transient = False
+        self.transient_certs_created = []
+
+        self.options = {
+            "debug" : False,
+            "beta" : False,
+            "ipv6" : True,
+            "timeout" : 600,
+            "short_timeout" : 5,
+            "width" : 72,
+            "auto_follow_redirects" : True,
+            "tls_mode" : "tofu",
+            "archives_size" : 200,
+            "history_size" : 200,
+            "max_size_download" : 10,
+            "editor" : None,
+            "download_images_first" : True,
+            "redirects" : True,
+            # the wikipedia entry needs two %s, one for lang, other for search
+            "wikipedia" : "gemini://vault.transjovian.org:1965/search/%s/%s",
+            "search"    : "gemini://kennedy.gemi.dev/search?%s",
+            "accept_bad_ssl_certificates" : False,
+        }
+        
+        self.redirects = {
+            "twitter.com" : "nitter.42l.fr",
+            "facebook.com" : "blocked",
+            "google-analytics.com" : "blocked",
+            "youtube.com" : "yewtu.be",
+            "reddit.com"  : "teddit.net",
+            "old.reddit.com": "teddit.net",
+            "medium.com"  : "scribe.rip",
+
+        }
+        global TERM_WIDTH
+        TERM_WIDTH = self.options["width"]
+        self.log = {
+            "start_time": time.time(),
+            "requests": 0,
+            "ipv4_requests": 0,
+            "ipv6_requests": 0,
+            "bytes_recvd": 0,
+            "ipv4_bytes_recvd": 0,
+            "ipv6_bytes_recvd": 0,
+            "dns_failures": 0,
+            "refused_connections": 0,
+            "reset_connections": 0,
+            "timeouts": 0,
+            "cache_hits": 0,
+        }
+
+        self._connect_to_tofu_db()
+
+    def complete_list(self,text,line,begidx,endidx):
+        allowed = []
+        cmds = ["create","edit","subscribe","freeze","normal","delete","help"]
+        lists = self.list_lists()
+        words = len(line.split())
+        # We need to autocomplete listname for the first or second argument
+        # If the first one is a cmds
+        if words <= 1:
+            allowed = lists + cmds
+        elif words == 2:
+            # if text, the completing word is the second
+            cond = bool(text)
+            if text:
+                allowed = lists + cmds
+            else:
+                current_cmd = line.split()[1]
+                if current_cmd in ["help", "create"]:
+                    allowed = []
+                elif current_cmd in cmds:
+                    allowed = lists 
+        elif words == 3 and text != "":
+            current_cmd = line.split()[1]
+            if current_cmd in ["help", "create"]:
+                allowed = []
+            elif current_cmd in cmds:
+                allowed = lists 
+        return [i+" " for i in allowed if i.startswith(text)]
+
+    def complete_add(self,text,line,begidx,endidx):
+        if len(line.split()) == 2 and text != "":
+            allowed = self.list_lists()
+        elif len(line.split()) == 1:
+            allowed = self.list_lists()
+        else:
+            allowed = []
+        return [i+" " for i in allowed if i.startswith(text)]
+    def complete_move(self,text,line,begidx,endidx):
+        return self.complete_add(text,line,begidx,endidx)
+
+    def _connect_to_tofu_db(self):
+
+        db_path = os.path.join(_CONFIG_DIR, "tofu.db")
+        self.db_conn = sqlite3.connect(db_path)
+        self.db_cur = self.db_conn.cursor()
+
+        self.db_cur.execute("""CREATE TABLE IF NOT EXISTS cert_cache
+            (hostname text, address text, fingerprint text,
+            first_seen date, last_seen date, count integer)""")
+
+    def _go_to_gi(self, gi, update_hist=True, check_cache=True, handle=True,\
+                                                mode=None,limit_size=False):
+        """This method might be considered "the heart of Offpunk".
+        Everything involved in fetching a gemini resource happens here:
+        sending the request over the network, parsing the response, 
+        storing the response in a temporary file, choosing
+        and calling a handler program, and updating the history.
+        Nothing is returned."""
+        if not gi:
+            return
+        # Don't try to speak to servers running other protocols
+        elif gi.scheme == "mailto":
+            if handle and not self.sync_only:
+                resp = input("Send an email to %s Y/N? " %gi.path)
+                self.gi = gi
+                if resp.strip().lower() in ("y", "yes"):
+                    if _HAS_XDGOPEN :
+                        run("xdg-open mailto:%s", parameter=gi.path ,direct_output=True)
+                    else:
+                        print("Cannot find a mail client to send mail to %s" %gi.path)
+                        print("Please install xdg-open (usually from xdg-util package)")
+            return
+        elif gi.scheme not in ["file","list", "rrtp"] and gi.scheme not in standard_ports \
+                                                                and not self.sync_only:
+            print("Sorry, no support for {} links.".format(gi.scheme))
+            return
+
+        if not mode:
+            mode = gi.last_mode
+        # Obey permanent redirects
+        if gi.url in self.permanent_redirects:
+            new_gi = GeminiItem(self.permanent_redirects[gi.url], name=gi.name)
+            self._go_to_gi(new_gi,mode=mode)
+            return
+        
+        # Use cache or mark as to_fetch if resource is not cached
+        # Why is this code useful ? It set the mimetype !
+        if self.offline_only:
+            if not gi.is_cache_valid():
+                self.get_list("to_fetch")
+                r = self.list_add_line("to_fetch",gi=gi,verbose=False)
+                if r:
+                    print("%s not available, marked for syncing"%gi.url)
+                else:
+                    print("%s already marked for syncing"%gi.url)
+                return
+        # check if local file exists.
+        if gi.local and not os.path.exists(gi.path):
+            print("Local file %s does not exist!" %gi.path)
+            return
+
+        elif not self.offline_only and not gi.local:
+            try:
+                if gi.scheme in ("http", "https"):
+                    if self.support_http:
+                        if limit_size:
+                            # Let’s cap automatic downloads to 20Mo
+                            max_download = int(self.options["max_size_download"])*1000000
+                        else:
+                            max_download = None
+                        gi = self._fetch_http(gi,max_length=max_download)
+                    elif handle and not self.sync_only:
+                        if not _DO_HTTP:
+                            print("Install python3-requests to handle http requests natively")
+                        webbrowser.open_new_tab(gi.url)
+                        return
+                    else:
+                        return
+                elif gi.scheme in ("gopher"):
+                    gi = self._fetch_gopher(gi,timeout=self.options["short_timeout"])
+                elif gi.scheme in ("finger"):
+                    gi = self._fetch_finger(gi,timeout=self.options["short_timeout"])
+                elif gi.scheme in ("spartan"):
+                    gi = self._fetch_spartan(gi)
+                elif gi.scheme in ("rrtp"):
+                    gi = self._fetch_rrtp(gi)
+                else:
+                    gi = self._fetch_over_network(gi)
+            except UserAbortException:
+                return
+            except Exception as err:
+                gi.set_error(err)
+                # Print an error message
+                # we fail silently when sync_only
+                print_error = not self.sync_only
+                if isinstance(err, socket.gaierror):
+                    self.log["dns_failures"] += 1
+                    if print_error:
+                        print("ERROR: DNS error!")
+                elif isinstance(err, ConnectionRefusedError):
+                    self.log["refused_connections"] += 1
+                    if print_error:
+                        print("ERROR1: Connection refused!")
+                elif isinstance(err, ConnectionResetError):
+                    self.log["reset_connections"] += 1
+                    if print_error:
+                        print("ERROR2: Connection reset!")
+                elif isinstance(err, (TimeoutError, socket.timeout)):
+                    self.log["timeouts"] += 1
+                    if print_error:
+                        print("""ERROR3: Connection timed out!
+        Slow internet connection?  Use 'set timeout' to be more patient.""")
+                elif isinstance(err, FileExistsError):
+                    print("""ERROR5: Trying to create a directory which already exists
+                            in the cache : """)
+                    print(err)
+                elif isinstance(err,requests.exceptions.SSLError):
+                    print("""ERROR6: Bad SSL certificate:\n""")
+                    print(err)
+                    print("""\n If you know what you are doing, you can try to accept bad certificates with the following command:\n""")
+                    print("""set accept_bad_ssl_certificates True""")
+                else:
+                    if print_error:
+                        print("ERROR4: " + str(type(err)) + " : " + str(err))
+                        print("\n" + str(err.with_traceback(None)))
+                return
+
+        # Pass file to handler, unless we were asked not to
+        if gi :
+            display = handle and not self.sync_only
+            if display and _RENDER_IMAGE and self.options["download_images_first"] \
+                                                        and not self.offline_only:
+                # We download images first
+                for image in gi.get_images(mode=mode):
+                    if image and image.startswith("http"):
+                        img_gi = GeminiItem(image)
+                        if not img_gi.is_cache_valid():
+                            width = term_width() - 1
+                            toprint = "Downloading %s" %image
+                            toprint = toprint[:width]
+                            toprint += " "*(width-len(toprint))
+                            print(toprint,end="\r")
+                            self._go_to_gi(img_gi, update_hist=False, check_cache=True, \
+                                                handle=False,limit_size=True)
+            if display and gi.display(mode=mode):
+                self.index = gi.get_links()
+                self.page_index = 0
+                self.index_index = -1
+                # Update state (external files are not added to history)
+                self.gi = gi
+                if update_hist and not self.sync_only:
+                    self._update_history(gi)
+            elif display :
+                cmd_str = self._get_handler_cmd(gi.get_mime())
+                try:
+                    # get body (tmpfile) from gi !
+                    run(cmd_str, parameter=gi.get_body(as_file=True), direct_output=True)
+                except FileNotFoundError:
+                    print("Handler program %s not found!" % shlex.split(cmd_str)[0])
+                    print("You can use the ! command to specify another handler program or pipeline.")
+
+    def _fetch_http(self,gi,max_length=None):
+        def set_error(item,length,max_length):
+            err = "Size of %s is %s Mo\n"%(item.url,length)
+            err += "Offpunk only download automatically content under %s Mo\n" %(max_length/1000000)
+            err += "To retrieve this content anyway, type 'reload'." 
+            item.set_error(err)
+            return item
+        header = {}
+        header["User-Agent"] = "Offpunk browser v%s"%_VERSION
+        parsed = urllib.parse.urlparse(gi.url)
+        # Code to translate URLs to better frontends (think twitter.com -> nitter)
+        if self.options["redirects"]:
+            netloc = parsed.netloc
+            if netloc.startswith("www."):
+                netloc = netloc[4:]
+            if netloc in self.redirects:
+                if self.redirects[netloc] == "blocked":
+                    text = "This website has been blocked.\n"
+                    text += "Use the redirect command to unblock it."
+                    gi.write_body(text,"text/gemini")
+                    return gi
+                else:
+                    parsed = parsed._replace(netloc = self.redirects[netloc])
+        url = urllib.parse.urlunparse(parsed)
+        with requests.get(url,headers=header, stream=True,timeout=5) as response:
+            #print("This is header for %s"%gi.url)
+            #print(response.headers)
+            if "content-type" in response.headers:
+                mime = response.headers['content-type']
+            else:
+                mime = None
+            if "content-length" in response.headers:
+                length = int(response.headers['content-length'])
+            else:
+                length = 0
+            if max_length and length > max_length:
+                response.close()
+                return set_error(gi,str(length/1000000),max_length)
+            elif max_length and length == 0:
+                body = b''
+                downloaded = 0
+                for r in response.iter_content():
+                    body += r
+                    #We divide max_size for streamed content
+                    #in order to catch them faster
+                    size = sys.getsizeof(body)
+                    max = max_length/2
+                    current = round(size*100/max,0)
+                    if current > downloaded:
+                        downloaded = current
+                        print("  -> Receiving stream: %s%% of allowed data"%downloaded,end='\r')
+                    #print("size: %s (%s\% of maxlenght)"%(size,size/max_length))
+                    if size > max_length/2:
+                        response.close()
+                        return set_error(gi,"streaming",max_length)
+                response.close()
+            else:
+                body = response.content
+                response.close()
+        if mime and "text/" in mime:
+            body = body.decode("UTF-8","replace")
+        gi.write_body(body,mime)
+        return 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
+        if parsed.path and parsed.path[0] == "/" and len(parsed.path) > 1:
+            splitted = parsed.path.split("/")
+            # We check if we have well a gopher type
+            if len(splitted[1]) == 1:
+                itemtype = parsed.path[1]
+                selector = parsed.path[2:]
+            else:
+                itemtype = "1"
+                selector = parsed.path
+        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:
+            request = selector
+        request += "\r\n"
+        s.sendall(request.encode("UTF-8"))
+        response = s.makefile("rb").read()
+        # Transcode response into UTF-8
+        #if itemtype in ("0","1","h"):
+        if not itemtype in ("9","g","I","s"):
+            # Try most common encodings
+            for encoding in ("UTF-8", "ISO-8859-1"):
+                try:
+                    response = response.decode("UTF-8")
+                    break
+                except UnicodeDecodeError:
+                    pass
+            else:
+                # try to find encoding
+                #if _HAS_CHARDET:
+                detected = chardet.detect(response)
+                response = response.decode(detected["encoding"])
+                #else:
+                    #raise UnicodeDecodeError
+        if itemtype == "0":
+            mime = "text/gemini"
+        elif itemtype == "1":
+            mime = "text/gopher"
+        elif itemtype == "h":
+            mime = "text/html"
+        elif itemtype in ("9","g","I","s"):
+            mime = None
+        else:
+            # by default, we should consider Gopher
+            mime = "text/gopher"
+        gi.write_body(response,mime)
+        return gi
+
+    def _fetch_finger(self,gi,timeout=10):
+        if not looks_like_url(gi.url):
+            print("%s is not a valid url" %gi.url)
+        parsed = urllib.parse.urlparse(gi.url)
+        host = parsed.hostname
+        port = parsed.port or standard_ports["finger"]
+        query = parsed.path.lstrip("/") + "\r\n"
+        with socket.create_connection((host,port)) as sock:
+            sock.settimeout(timeout)
+            sock.send(query.encode())
+            response = sock.makefile("rb").read().decode("UTF-8")
+            gi.write_body(response,"text/plain")
+        return gi
+
+    # Copied from reference spartan client by Michael Lazar
+    def _fetch_spartan(self,gi):
+        url_parts = urllib.parse.urlparse(gi.url)
+        host = url_parts.hostname
+        port = url_parts.port or 300
+        path = url_parts.path or "/"
+        query = url_parts.query
+
+        redirect_url = None
+
+        with socket.create_connection((host,port)) as sock:
+            if query:
+                data = urllib.parse.unquote_to_bytes(query)
+            else:
+                data = b""
+            encoded_host = host.encode("idna")
+            ascii_path = urllib.parse.unquote_to_bytes(path)
+            encoded_path = urllib.parse.quote_from_bytes(ascii_path).encode("ascii")
+            sock.send(b"%s %s %d\r\n" % (encoded_host,encoded_path,len(data)))
+            fp = sock.makefile("rb")
+            response = fp.readline(4096).decode("ascii").strip("\r\n")
+            parts = response.split(" ",maxsplit=1)
+            code,meta = int(parts[0]),parts[1]
+            if code == 2:
+                body = fp.read()
+                if meta.startswith("text"):
+                    body = body.decode("UTF-8")
+                gi.write_body(body,meta)
+            elif code == 3:
+                redirect_url = url_parts._replace(path=meta).geturl()
+            else:
+                gi.set_error("Spartan code %s: Error %s"%(code,meta))
+        if redirect_url:
+            gi = GeminiItem(redirect_url)
+            self._fetch_spartan(gi)
+        return gi
+
+    def _fetch_rrtp(self, gi):
+        request = RRTPRequest()
+        r = request.get(gi.url)
+        gi.write_body(r.body, r.header)
+        return gi
+
+    # fetch_over_network will modify with gi.write_body(body,mime)
+    # before returning the gi
+    def _fetch_over_network(self, gi):
+        
+        # Be careful with client certificates!
+        # Are we crossing a domain boundary?
+        if self.active_cert_domains and gi.host not in self.active_cert_domains:
+            if self.active_is_transient:
+                print("Permanently delete currently active transient certificate?")
+                resp = input("Y/N? ")
+                if resp.strip().lower() in ("y", "yes"):
+                    print("Destroying certificate.")
+                    self._deactivate_client_cert()
+                else:
+                    print("Staying here.")
+                    raise UserAbortException()
+            else:
+                print("PRIVACY ALERT: Deactivate client cert before connecting to a new domain?")
+                resp = input("Y/N? ")
+                if resp.strip().lower() in ("n", "no"):
+                    print("Keeping certificate active for {}".format(gi.host))
+                else:
+                    print("Deactivating certificate.")
+                    self._deactivate_client_cert()
+
+        # Suggest reactivating previous certs
+        if not self.client_certs["active"] and gi.host in self.client_certs:
+            print("PRIVACY ALERT: Reactivate previously used client cert for {}?".format(gi.host))
+            resp = input("Y/N? ")
+            if resp.strip().lower() in ("y", "yes"):
+                self._activate_client_cert(*self.client_certs[gi.host])
+            else:
+                print("Remaining unidentified.")
+                self.client_certs.pop(gi.host)
+
+        # Is this a local file?
+        if gi.local:
+            address, f = None, open(gi.path, "rb")
+        else:
+            address, f = self._send_request(gi)
+
+        # Spec dictates  should not exceed 1024 bytes,
+        # so maximum valid header length is 1027 bytes.
+        header = f.readline(1027)
+        header = urllib.parse.unquote(header.decode("UTF-8"))
+        if not header or header[-1] != '\n':
+            raise RuntimeError("Received invalid header from server!")
+        header = header.strip()
+        self._debug("Response header: %s." % header)
+        # Validate header
+        status, meta = header.split(maxsplit=1)
+        if len(meta) > 1024 or len(status) != 2 or not status.isnumeric():
+            f.close()
+            raise RuntimeError("Received invalid header from server!")
+
+        # Update redirect loop/maze escaping state
+        if not status.startswith("3"):
+            self.previous_redirectors = set()
+
+        # Handle non-SUCCESS headers, which don't have a response body
+        # Inputs
+        if status.startswith("1"):
+            if self.sync_only:
+                return None
+            else:
+                print(meta)
+                if status == "11":
+                    user_input = getpass.getpass("> ")
+                else:
+                    user_input = input("> ")
+                return self._fetch_over_network(gi.query(user_input))
+
+        # Redirects
+        elif status.startswith("3"):
+            new_gi = GeminiItem(gi.absolutise_url(meta))
+            if new_gi.url == gi.url:
+                raise RuntimeError("URL redirects to itself!")
+            elif new_gi.url in self.previous_redirectors:
+                raise RuntimeError("Caught in redirect loop!")
+            elif len(self.previous_redirectors) == _MAX_REDIRECTS:
+                raise RuntimeError("Refusing to follow more than %d consecutive redirects!" % _MAX_REDIRECTS)
+            elif self.sync_only:
+                follow = self.automatic_choice
+            # Never follow cross-domain redirects without asking
+            elif new_gi.host != gi.host:
+                follow = input("Follow cross-domain redirect to %s? (y/n) " % new_gi.url)
+            # Never follow cross-protocol redirects without asking
+            elif new_gi.scheme != gi.scheme:
+                follow = input("Follow cross-protocol redirect to %s? (y/n) " % new_gi.url)
+            # Don't follow *any* redirect without asking if auto-follow is off
+            elif not self.options["auto_follow_redirects"]:
+                follow = input("Follow redirect to %s? (y/n) " % new_gi.url)
+            # Otherwise, follow away
+            else:
+                follow = "yes"
+            if follow.strip().lower() not in ("y", "yes"):
+                raise UserAbortException()
+            self._debug("Following redirect to %s." % new_gi.url)
+            self._debug("This is consecutive redirect number %d." % len(self.previous_redirectors))
+            self.previous_redirectors.add(gi.url)
+            if status == "31":
+                # Permanent redirect
+                self.permanent_redirects[gi.url] = new_gi.url
+            return self._fetch_over_network(new_gi)
+
+        # Errors
+        elif status.startswith("4") or status.startswith("5"):
+            raise RuntimeError(meta)
+
+        # Client cert
+        elif status.startswith("6"):
+            self._handle_cert_request(meta)
+            return self._fetch_over_network(gi)
+
+        # Invalid status
+        elif not status.startswith("2"):
+            raise RuntimeError("Server returned undefined status code %s!" % status)
+
+        # If we're here, this must be a success and there's a response body
+        assert status.startswith("2")
+        
+        mime = meta
+        # Read the response body over the network
+        fbody = f.read()
+        # DEFAULT GEMINI MIME
+        if mime == "":
+            mime = "text/gemini; charset=utf-8"
+        shortmime, mime_options = parse_mime(mime)
+        if "charset" in mime_options:
+            try:
+                codecs.lookup(mime_options["charset"])
+            except LookupError:
+                raise RuntimeError("Header declared unknown encoding %s" % value)
+        if shortmime.startswith("text/"):
+            #Get the charset and default to UTF-8 in none
+            encoding = mime_options.get("charset", "UTF-8")
+            try:
+                body = fbody.decode(encoding)
+            except UnicodeError:
+                raise RuntimeError("Could not decode response body using %s\
+                                    encoding declared in header!" % encoding)
+        else:
+            body = fbody
+        gi.write_body(body,mime)    
+        return gi
+
+    def _send_request(self, gi):
+        """Send a selector to a given host and port.
+        Returns the resolved address and binary file with the reply."""
+        host, port = gi.host, gi.port
+        host = host.encode("idna").decode()
+        # Do DNS resolution
+        addresses = self._get_addresses(host, port)
+
+        # Prepare TLS context
+        protocol = ssl.PROTOCOL_TLS_CLIENT if sys.version_info.minor >=6 else ssl.PROTOCOL_TLSv1_2
+        context = ssl.SSLContext(protocol)
+        # Use CAs or TOFU
+        if self.options["tls_mode"] == "ca":
+            context.verify_mode = ssl.CERT_REQUIRED
+            context.check_hostname = True
+            context.load_default_certs()
+        else:
+            context.check_hostname = False
+            context.verify_mode = ssl.CERT_NONE
+        # Impose minimum TLS version
+        ## In 3.7 and above, this is easy...
+        if sys.version_info.minor >= 7:
+            context.minimum_version = ssl.TLSVersion.TLSv1_2
+        ## Otherwise, it seems very hard...
+        ## The below is less strict than it ought to be, but trying to disable
+        ## TLS v1.1 here using ssl.OP_NO_TLSv1_1 produces unexpected failures
+        ## with recent versions of OpenSSL.  What a mess...
+        else:
+            context.options |= ssl.OP_NO_SSLv3
+            context.options |= ssl.OP_NO_SSLv2
+        # Try to enforce sensible ciphers
+        try:
+            context.set_ciphers("AESGCM+ECDHE:AESGCM+DHE:CHACHA20+ECDHE:CHACHA20+DHE:!DSS:!SHA1:!MD5:@STRENGTH")
+        except ssl.SSLError:
+            # Rely on the server to only support sensible things, I guess...
+            pass
+        # Load client certificate if needed
+        if self.client_certs["active"]:
+            certfile, keyfile = self.client_certs["active"]
+            context.load_cert_chain(certfile, keyfile)
+        
+        # Connect to remote host by any address possible
+        err = None
+        for address in addresses:
+            self._debug("Connecting to: " + str(address[4]))
+            s = socket.socket(address[0], address[1])
+            if self.sync_only:
+                timeout = self.options["short_timeout"]
+            else:
+                timeout = self.options["timeout"]
+            s.settimeout(timeout)
+            s = context.wrap_socket(s, server_hostname = host)
+            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 sys.version_info.minor >=5:
+            self._debug("Established {} connection.".format(s.version()))
+        self._debug("Cipher is: {}.".format(s.cipher()))
+
+        # Do TOFU
+        if self.options["tls_mode"] != "ca":
+            cert = s.getpeercert(binary_form=True)
+            self._validate_cert(address[4][0], host, cert)
+
+        # Remember that we showed the current cert to this domain...
+        if self.client_certs["active"]:
+            self.active_cert_domains.append(host)
+            self.client_certs[host] = self.client_certs["active"]
+
+        # Send request and wrap response in a file descriptor
+        self._debug("Sending %s" % gi.url)
+        s.sendall((gi.url + CRLF).encode("UTF-8"))
+        mf= s.makefile(mode = "rb")
+        return address, mf
+
+    def _get_addresses(self, host, port):
+        # DNS lookup - will get IPv4 and IPv6 records if IPv6 is enabled
+        if ":" in host:
+            # This is likely a literal IPv6 address, so we can *only* ask for
+            # IPv6 addresses or getaddrinfo will complain
+            family_mask = socket.AF_INET6
+        elif socket.has_ipv6 and self.options["ipv6"]:
+            # Accept either IPv4 or IPv6 addresses
+            family_mask = 0
+        else:
+            # IPv4 only
+            family_mask = socket.AF_INET
+        addresses = socket.getaddrinfo(host, port, family=family_mask,
+                type=socket.SOCK_STREAM)
+        # Sort addresses so IPv6 ones come first
+        addresses.sort(key=lambda add: add[0] == socket.AF_INET6, reverse=True)
+
+        return addresses
+
+
+    def _handle_cert_request(self, meta):
+        print("SERVER SAYS: ", meta)
+        # Present different messages for different 6x statuses, but
+        # handle them the same.
+        if status in ("64", "65"):
+            print("The server rejected your certificate because it is either expired or not yet valid.")
+        elif status == "63":
+            print("The server did not accept your certificate.")
+            print("You may need to e.g. coordinate with the admin to get your certificate fingerprint whitelisted.")
+        else:
+            print("The site {} is requesting a client certificate.".format(gi.host))
+            print("This will allow the site to recognise you across requests.")
+
+        # Give the user choices
+        print("What do you want to do?")
+        print("1. Give up.")
+        print("2. Generate a new transient certificate.")
+        print("3. Generate a new persistent certificate.")
+        print("4. Load a previously generated persistent.")
+        print("5. Load certificate from an external file.")
+        if self.sync_only:
+            choice = 1
+        else:
+            choice = input("> ").strip()
+        if choice == "2":
+            self._generate_transient_cert_cert()
+        elif choice == "3":
+            self._generate_persistent_client_cert()
+        elif choice == "4":
+            self._choose_client_cert()
+        elif choice == "5":
+            self._load_client_cert()
+        else:
+            print("Giving up.")
+            raise UserAbortException()
+
+    def _validate_cert(self, address, host, cert):
+        """
+        Validate a TLS certificate in TOFU mode.
+
+        If the cryptography module is installed:
+         - Check the certificate Common Name or SAN matches `host`
+         - Check the certificate's not valid before date is in the past
+         - Check the certificate's not valid after date is in the future
+
+        Whether the cryptography module is installed or not, check the
+        certificate's fingerprint against the TOFU database to see if we've
+        previously encountered a different certificate for this IP address and
+        hostname.
+        """
+        now = datetime.datetime.utcnow()
+        if _HAS_CRYPTOGRAPHY:
+            # Using the cryptography module we can get detailed access
+            # to the properties of even self-signed certs, unlike in
+            # the standard ssl library...
+            c = x509.load_der_x509_certificate(cert, _BACKEND)
+            # Check certificate validity dates
+            if c.not_valid_before >= now:
+                raise CertificateError("Certificate not valid until: {}!".format(c.not_valid_before))
+            elif c.not_valid_after <= now:
+                raise CertificateError("Certificate expired as of: {})!".format(c.not_valid_after))
+
+            # Check certificate hostnames
+            names = []
+            common_name = c.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)
+            if common_name:
+                names.append(common_name[0].value)
+            try:
+                names.extend([alt.value for alt in c.extensions.get_extension_for_oid(x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME).value])
+            except x509.ExtensionNotFound:
+                pass
+            names = set(names)
+            for name in names:
+                try:
+                    ssl._dnsname_match(name, host)
+                    break
+                except CertificateError:
+                    continue
+            else:
+                # If we didn't break out, none of the names were valid
+                raise CertificateError("Hostname does not match certificate common name or any alternative names.")
+
+        sha = hashlib.sha256()
+        sha.update(cert)
+        fingerprint = sha.hexdigest()
+
+        # Have we been here before?
+        self.db_cur.execute("""SELECT fingerprint, first_seen, last_seen, count
+            FROM cert_cache
+            WHERE hostname=? AND address=?""", (host, address))
+        cached_certs = self.db_cur.fetchall()
+
+        # If so, check for a match
+        if cached_certs:
+            max_count = 0
+            most_frequent_cert = None
+            for cached_fingerprint, first, last, count in cached_certs:
+                if count > max_count:
+                    max_count = count
+                    most_frequent_cert = cached_fingerprint
+                if fingerprint == cached_fingerprint:
+                    # Matched!
+                    self._debug("TOFU: Accepting previously seen ({} times) certificate {}".format(count, fingerprint))
+                    self.db_cur.execute("""UPDATE cert_cache
+                        SET last_seen=?, count=?
+                        WHERE hostname=? AND address=? AND fingerprint=?""",
+                        (now, count+1, host, address, fingerprint))
+                    self.db_conn.commit()
+                    break
+            else:
+                certdir = os.path.join(_CONFIG_DIR, "cert_cache")
+                with open(os.path.join(certdir, most_frequent_cert+".crt"), "rb") as fp:
+                    previous_cert = fp.read()
+                if _HAS_CRYPTOGRAPHY:
+                    # Load the most frequently seen certificate to see if it has
+                    # expired
+                    previous_cert = x509.load_der_x509_certificate(previous_cert, _BACKEND)
+                    previous_ttl = previous_cert.not_valid_after - now
+                    print(previous_ttl)
+
+                self._debug("TOFU: Unrecognised certificate {}!  Raising the alarm...".format(fingerprint))
+                print("****************************************")
+                print("[SECURITY WARNING] Unrecognised certificate!")
+                print("The certificate presented for {} ({}) has never been seen before.".format(host, address))
+                print("This MIGHT be a Man-in-the-Middle attack.")
+                print("A different certificate has previously been seen {} times.".format(max_count))
+                if _HAS_CRYPTOGRAPHY:
+                    if previous_ttl < datetime.timedelta():
+                        print("That certificate has expired, which reduces suspicion somewhat.")
+                    else:
+                        print("That certificate is still valid for: {}".format(previous_ttl))
+                print("****************************************")
+                print("Attempt to verify the new certificate fingerprint out-of-band:")
+                print(fingerprint)
+                if self.sync_only:
+                    choice = self.automatic_choice
+                else:
+                    choice = input("Accept this new certificate? Y/N ").strip().lower()
+                if choice in ("y", "yes"):
+                    self.db_cur.execute("""INSERT INTO cert_cache
+                        VALUES (?, ?, ?, ?, ?, ?)""",
+                        (host, address, fingerprint, now, now, 1))
+                    self.db_conn.commit()
+                    with open(os.path.join(certdir, fingerprint+".crt"), "wb") as fp:
+                        fp.write(cert)
+                else:
+                    raise Exception("TOFU Failure!")
+
+        # If not, cache this cert
+        else:
+            self._debug("TOFU: Blindly trusting first ever certificate for this host!")
+            self.db_cur.execute("""INSERT INTO cert_cache
+                VALUES (?, ?, ?, ?, ?, ?)""",
+                (host, address, fingerprint, now, now, 1))
+            self.db_conn.commit()
+            certdir = os.path.join(_CONFIG_DIR, "cert_cache")
+            if not os.path.exists(certdir):
+                os.makedirs(certdir)
+            with open(os.path.join(certdir, fingerprint+".crt"), "wb") as fp:
+                fp.write(cert)
+
+    def _get_handler_cmd(self, mimetype):
+        # Now look for a handler for this mimetype
+        # Consider exact matches before wildcard matches
+        exact_matches = []
+        wildcard_matches = []
+        for handled_mime, cmd_str in _MIME_HANDLERS.items():
+            if "*" in handled_mime:
+                wildcard_matches.append((handled_mime, cmd_str))
+            else:
+                exact_matches.append((handled_mime, cmd_str))
+        for handled_mime, cmd_str in exact_matches + wildcard_matches:
+            if fnmatch.fnmatch(mimetype, handled_mime):
+                break
+        else:
+            # Use "xdg-open" as a last resort.
+            if _HAS_XDGOPEN:
+                cmd_str = "xdg-open %s"
+            else:
+                cmd_str = "echo \"Can’t find how to open \"%s"
+                print("Please install xdg-open (usually from xdg-util package)")
+        self._debug("Using handler: %s" % cmd_str)
+        return cmd_str
+
+    #TODO: remove format_geminiitem
+    def _format_geminiitem(self, index, gi, url=False):
+        if not gi:
+            line = "[%s] - No valid URL"%index
+        else:
+            protocol = "" if gi.scheme == "gemini" else " %s" % gi.scheme
+            line = "[%d%s] %s" % (index, protocol, gi.name or gi.url)
+            if gi.name and url:
+                line += " (%s)" % gi.url
+        return line
+
+    @needs_gi
+    def _show_lookup(self, offset=0, end=None, url=False):
+        for n, gi in enumerate(self.gi.get_links()[offset:end]):
+            print(self._format_geminiitem(n+offset+1, gi, url))
+
+    def _update_history(self, gi):
+        # We never update while in sync_only
+        if self.sync_only:
+            return
+        # We don’t add lists to history
+        #if not gi or os.path.join(_DATA_DIR,"lists") in gi.url:
+        #    return
+        histlist = self.get_list("history")
+        links = self.list_get_links("history")
+        # avoid duplicate
+        length = len(links)
+        if length > self.options["history_size"]:
+            length = self.options["history_size"]
+        if length > 0 and links[self.hist_index] == gi:
+            return
+        self.list_add_top("history",limit=self.options["history_size"],truncate_lines=self.hist_index)
+        self.hist_index = 0
+
+
+    def _log_visit(self, gi, address, size):
+        if not address:
+            return
+        self.log["requests"] += 1
+        self.log["bytes_recvd"] += size
+        self.visited_hosts.add(address)
+        if address[0] == socket.AF_INET:
+            self.log["ipv4_requests"] += 1
+            self.log["ipv4_bytes_recvd"] += size
+        elif address[0] == socket.AF_INET6:
+            self.log["ipv6_requests"] += 1
+            self.log["ipv6_bytes_recvd"] += size
+
+    def _debug(self, debug_text):
+        if not self.options["debug"]:
+            return
+        debug_text = "\x1b[0;32m[DEBUG] " + debug_text + "\x1b[0m"
+        print(debug_text)
+
+    def _load_client_cert(self):
+        """
+        Interactively load a TLS client certificate from the filesystem in PEM
+        format.
+        """
+        print("Loading client certificate file, in PEM format (blank line to cancel)")
+        certfile = input("Certfile path: ").strip()
+        if not certfile:
+            print("Aborting.")
+            return
+        certfile = os.path.expanduser(certfile)
+        if not os.path.isfile(certfile):
+            print("Certificate file {} does not exist.".format(certfile))
+            return
+        print("Loading private key file, in PEM format (blank line to cancel)")
+        keyfile = input("Keyfile path: ").strip()
+        if not keyfile:
+            print("Aborting.")
+            return
+        keyfile = os.path.expanduser(keyfile)
+        if not os.path.isfile(keyfile):
+            print("Private key file {} does not exist.".format(keyfile))
+            return
+        self._activate_client_cert(certfile, keyfile)
+
+    def _generate_transient_cert_cert(self):
+        """
+        Use `openssl` command to generate a new transient client certificate
+        with 24 hours of validity.
+        """
+        certdir = os.path.join(_CONFIG_DIR, "transient_certs")
+        name = str(uuid.uuid4())
+        self._generate_client_cert(certdir, name, transient=True)
+        self.active_is_transient = True
+        self.transient_certs_created.append(name)
+
+    def _generate_persistent_client_cert(self):
+        """
+        Interactively use `openssl` command to generate a new persistent client
+        certificate with one year of validity.
+        """
+        certdir = os.path.join(_CONFIG_DIR, "client_certs")
+        print("What do you want to name this new certificate?")
+        print("Answering `mycert` will create `{0}/mycert.crt` and `{0}/mycert.key`".format(certdir))
+        name = input("> ")
+        if not name.strip():
+            print("Aborting.")
+            return
+        self._generate_client_cert(certdir, name)
+
+    def _generate_client_cert(self, certdir, basename, transient=False):
+        """
+        Use `openssl` binary to generate a client certificate (which may be
+        transient or persistent) and save the certificate and private key to the
+        specified directory with the specified basename.
+        """
+        if not os.path.exists(certdir):
+            os.makedirs(certdir)
+        certfile = os.path.join(certdir, basename+".crt")
+        keyfile = os.path.join(certdir, basename+".key")
+        cmd = "openssl req -x509 -newkey rsa:2048 -days {} -nodes -keyout {} -out {}".format(1 if transient else 365, keyfile, certfile)
+        if transient:
+            cmd += " -subj '/CN={}'".format(basename)
+        os.system(cmd)
+        self._activate_client_cert(certfile, keyfile)
+
+    def _choose_client_cert(self):
+        """
+        Interactively select a previously generated client certificate and
+        activate it.
+        """
+        certdir = os.path.join(_CONFIG_DIR, "client_certs")
+        certs = glob.glob(os.path.join(certdir, "*.crt"))
+        if len(certs) == 0:
+            print("There are no previously generated certificates.")
+            return
+        certdir = {}
+        for n, cert in enumerate(certs):
+            certdir[str(n+1)] = (cert, os.path.splitext(cert)[0] + ".key")
+            print("{}. {}".format(n+1, os.path.splitext(os.path.basename(cert))[0]))
+        choice = input("> ").strip()
+        if choice in certdir:
+            certfile, keyfile = certdir[choice]
+            self._activate_client_cert(certfile, keyfile)
+        else:
+            print("What?")
+
+    def _activate_client_cert(self, certfile, keyfile):
+        self.client_certs["active"] = (certfile, keyfile)
+        self.active_cert_domains = []
+        self.prompt = self.cert_prompt + "+" + os.path.basename(certfile).replace('.crt','') + "> " + "\001\x1b[0m\002"
+        self._debug("Using ID {} / {}.".format(*self.client_certs["active"]))
+
+    def _deactivate_client_cert(self):
+        if self.active_is_transient:
+            for filename in self.client_certs["active"]:
+                os.remove(filename)
+            for domain in self.active_cert_domains:
+                self.client_certs.pop(domain)
+        self.client_certs["active"] = None
+        self.active_cert_domains = []
+        self.prompt = self.no_cert_prompt
+        self.active_is_transient = False
+
+    # Cmd implementation follows
+
+    def default(self, line):
+        if line.strip() == "EOF":
+            return self.onecmd("quit")
+        elif line.strip() == "..":
+            return self.do_up()
+        elif line.startswith("/"):
+            return self.do_find(line[1:])
+        # Expand abbreviated commands
+        first_word = line.split()[0].strip()
+        if first_word in _ABBREVS:
+            full_cmd = _ABBREVS[first_word]
+            expanded = line.replace(first_word, full_cmd, 1)
+            return self.onecmd(expanded)
+        # Try to access it like an URL
+        if looks_like_url(line):
+            return self.do_go(line)
+        # Try to parse numerical index for lookup table
+        try:
+            n = int(line.strip())
+        except ValueError:
+            print("What?")
+            return
+        # if we have no GeminiItem, there's nothing to do
+        if self.gi is None:
+            print("No links to index")
+            return
+        try:
+            gi = self.gi.get_link(n)
+        except IndexError:
+            print ("Index too high!")
+            return
+
+        self.index_index = n
+        self._go_to_gi(gi)
+
+    ### Settings
+    def do_redirect(self,line):
+        """Display and manage the list of redirected URLs. This features is mostly useful to use privacy-friendly frontends for popular websites."""
+        if len(line.split()) == 1:
+            if line in self.redirects:
+                print("%s is redirected to %s" %(line,self.redirects[line]))
+            else:
+                print("Please add a destination to redirect %s" %line)
+        elif len(line.split()) >= 2:
+            orig, dest = line.split(" ",1)
+            if dest.lower() == "none":
+                if orig in self.redirects:
+                    self.redirects.pop(orig)
+                    print("Redirection for %s has been removed"%orig)
+                else:
+                    print("%s was not redirected. Nothing has changed."%orig)
+            elif dest.lower() == "block":
+                self.redirects[orig] = "blocked"
+                print("%s will now be blocked"%orig)
+            else:
+                self.redirects[orig] = dest
+                print("%s will now be redirected to %s" %(orig,dest))
+        else:
+            toprint="Current redirections:\n"
+            toprint+="--------------------\n"
+            for r in self.redirects:
+                toprint += ("%s\t->\t%s\n" %(r,self.redirects[r]))
+            toprint +="\nTo add new, use \"redirect origine.com destination.org\""
+            toprint +="\nTo remove a redirect, use \"redirect origine.com NONE\""
+            toprint +="\nTo completely block a website, use \"redirect origine.com BLOCK\""
+            print(toprint)
+
+    def do_set(self, line):
+        """View or set various options."""
+        if not line.strip():
+            # Show all current settings
+            for option in sorted(self.options.keys()):
+                print("%s   %s" % (option, self.options[option]))
+        elif len(line.split()) == 1 :
+            # Show current value of one specific setting
+            option = line.strip()
+            if option in self.options:
+                print("%s   %s" % (option, self.options[option]))
+            else:
+                print("Unrecognised option %s" % option)
+        else:
+            # Set value of one specific setting
+            option, value = line.split(" ", 1)
+            if option not in self.options:
+                print("Unrecognised option %s" % option)
+                return
+            # Validate / convert values
+            elif option == "tls_mode":
+                if value.lower() not in ("ca", "tofu"):
+                    print("TLS mode must be `ca` or `tofu`!")
+                    return
+            elif option == "accept_bad_ssl_certificates":
+                if not _DO_HTTP :
+                    print("accepting bad certificates only makes sense with HTTP requests")
+                    print("You need to install python3-request and other dependancies")
+                    print("Type \"version\" for more information")
+                    return
+                elif value.lower() == "false":
+                    print("Only high security certificates are now accepted")
+                    requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL:@SECLEVEL=2'
+                elif value.lower() == "true":
+                    print("Low security SSL certificates are now accepted")
+                    requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = 'ALL:@SECLEVEL=1'
+                else:
+                    print("accept_bad_ssl_certificates should be True or False")
+                    return
+            elif option == "width":
+                if value.isnumeric():
+                    value = int(value)
+                    print("changing width to ",value)
+                    global TERM_WIDTH
+                    TERM_WIDTH = value
+                else:
+                    print("%s is not a valid width (integer required)"%value)
+            elif value.isnumeric():
+                value = int(value)
+            elif value.lower() == "false":
+                value = False
+            elif value.lower() == "true":
+                value = True
+            else:
+                try:
+                    value = float(value)
+                except ValueError:
+                    pass
+            self.options[option] = value
+
+    def do_cert(self, line):
+        """Manage client certificates"""
+        print("Managing client certificates")
+        if self.client_certs["active"]:
+            print("Active certificate: {}".format(self.client_certs["active"][0]))
+        print("1. Deactivate client certificate.")
+        print("2. Generate new certificate.")
+        print("3. Load previously generated certificate.")
+        print("4. Load externally created client certificate from file.")
+        print("Enter blank line to exit certificate manager.")
+        choice = input("> ").strip()
+        if choice == "1":
+            print("Deactivating client certificate.")
+            self._deactivate_client_cert()
+        elif choice == "2":
+            self._generate_persistent_client_cert()
+        elif choice == "3":
+            self._choose_client_cert()
+        elif choice == "4":
+            self._load_client_cert()
+        else:
+            print("Aborting.")
+
+    def do_handler(self, line):
+        """View or set handler commands for different MIME types."""
+        if not line.strip():
+            # Show all current handlers
+            for mime in sorted(_MIME_HANDLERS.keys()):
+                print("%s   %s" % (mime, _MIME_HANDLERS[mime]))
+        elif len(line.split()) == 1:
+            mime = line.strip()
+            if mime in _MIME_HANDLERS:
+                print("%s   %s" % (mime, _MIME_HANDLERS[mime]))
+            else:
+                print("No handler set for MIME type %s" % mime)
+        else:
+            mime, handler = line.split(" ", 1)
+            _MIME_HANDLERS[mime] = handler
+            if "%s" not in handler:
+                print("Are you sure you don't want to pass the filename to the handler?")
+
+    def do_abbrevs(self, *args):
+        """Print all Offpunk command abbreviations."""
+        header = "Command Abbreviations:"
+        self.stdout.write("\n{}\n".format(str(header)))
+        if self.ruler:
+            self.stdout.write("{}\n".format(str(self.ruler * len(header))))
+        for k, v in _ABBREVS.items():
+            self.stdout.write("{:<7}  {}\n".format(k, v))
+        self.stdout.write("\n")
+
+    def do_offline(self, *args):
+        """Use Offpunk offline by only accessing cached content"""
+        if self.offline_only:
+            print("Offline and undisturbed.")
+        else:
+            self.offline_only = True
+            self.prompt = self.offline_prompt
+            print("Offpunk is now offline and will only access cached content")
+    
+    def do_online(self, *args):
+        """Use Offpunk online with a direct connection"""
+        if self.offline_only:    
+            self.offline_only = False
+            self.prompt = self.no_cert_prompt
+            print("Offpunk is online and will access the network")
+        else:
+            print("Already online. Try offline.")
+
+    def do_copy(self, arg):
+        """Copy the content of the last visited page as gemtext in the clipboard.
+Use with "url" as argument to only copy the adress.
+Use with "raw" to copy ANSI content as seen in your terminal (not gemtext).
+Use with "cache" to copy the path of the cached content."""
+        if self.gi:
+            if _HAS_XSEL:
+                args = arg.split()
+                if args and args[0] == "url":
+                    if len(args) > 1 and args[1].isdecimal():
+                        gi = self.index[int(args[1])-1]
+                        url = gi.url
+                    else:
+                        url = self.gi.url
+                    run("xsel -b -i", input=url, direct_output=True)
+                elif args and args[0] == "raw":
+                    run("xsel -b -i", input=open(self.gi.get_temp_filename(), "rb"), direct_output=True)
+                elif args and args[0] == "cache":
+                    run("xsel -b -i", input=self.gi.get_cache_path(), direct_output=True)
+                else:
+                    run("xsel -b -i", input=open(self.gi.get_body(as_file=True), "rb"), direct_output=True)
+            else:
+                print("Please install xsel to use copy")
+        else:
+            print("No content to copy, visit a page first")
+
+    ### Stuff for getting around
+    def do_go(self, line):
+        """Go to a gemini URL or marked item."""
+        line = line.strip()
+        if not line:
+            if shutil.which('xsel'):
+                clipboards = []
+                urls = []
+                for selec in ["-p","-s","-b"]:
+                    try:
+                        clipboards.append(run("xsel "+selec))
+                    except Exception as err:
+                        #print("Skippink clipboard %s because %s"%(selec,err))
+                        pass
+                for u in clipboards:
+                    if "://" in u and looks_like_url(u) and u not in urls :
+                        urls.append(u)
+                if len(urls) > 1:
+                    stri = "URLs in your clipboard\n" 
+                    counter = 0
+                    for u in urls:
+                        counter += 1
+                        stri += "[%s] %s\n"%(counter,u)
+                    stri += "Where do you want to go today ?> "
+                    ans = input(stri)
+                    if ans.isdigit() and 0 < int(ans) <= len(urls):
+                        self.do_go(urls[int(ans)-1])
+                elif len(urls) == 1:
+                    self.do_go(urls[0])
+                else:
+                    print("Go where? (hint: simply copy an URL in your clipboard)")
+            else:
+                print("Go where? (hint: install xsel to go to copied URLs)")
+
+        # First, check for possible marks
+        elif line in self.marks:
+            gi = self.marks[line]
+            self._go_to_gi(gi)
+        # or a local file
+        elif os.path.exists(os.path.expanduser(line)):
+            self._go_to_gi(GeminiItem(line))
+        # If this isn't a mark, treat it as a URL
+        elif looks_like_url(line):
+            self._go_to_gi(GeminiItem(line))
+        else:
+            print("%s is not a valid URL to go"%line)
+
+    @needs_gi
+    def do_reload(self, *args):
+        """Reload the current URL."""
+        if self.offline_only:
+            self.get_list("to_fetch")
+            r = self.list_add_line("to_fetch",gi=self.gi,verbose=False)
+            if r:
+                print("%s marked for syncing" %self.gi.url)
+            else:
+                print("%s already marked for syncing" %self.gi.url)
+        else:
+            self._go_to_gi(self.gi, check_cache=False)
+
+    @needs_gi
+    def do_up(self, *args):
+        """Go up one directory in the path.
+Take an integer as argument to go up multiple times."""
+        level = 1
+        if args[0].isnumeric():
+            level = int(args[0])
+        elif args[0] != "":
+            print("Up only take integer as arguments")
+        self._go_to_gi(self.gi.up(level=level))
+
+    def do_back(self, *args):
+        """Go back to the previous gemini item."""
+        histfile = self.get_list("history")
+        links = self.list_get_links("history")
+        if self.hist_index >= len(links) -1:
+            return
+        self.hist_index += 1
+        gi = links[self.hist_index]
+        self._go_to_gi(gi, update_hist=False)
+
+    def do_forward(self, *args):
+        """Go forward to the next gemini item."""
+        histfile = self.get_list("history")
+        links = self.list_get_links("history")
+        if self.hist_index <= 0:
+            return
+        self.hist_index -= 1
+        gi = links[self.hist_index]
+        self._go_to_gi(gi, update_hist=False)
+
+    @needs_gi
+    def do_root(self, *args):
+        """Go to root selector of the server hosting current item."""
+        self._go_to_gi(self.gi.root())
+
+    def do_tour(self, line):
+        """Add index items as waypoints on a tour, which is basically a FIFO
+queue of gemini items.
+
+`tour` or `t` alone brings you to the next item in your tour.
+Items can be added with `tour 1 2 3 4` or ranges like `tour 1-4`.
+All items in current menu can be added with `tour *`.
+All items in $LIST can be added with `tour $LIST`.
+Current item can be added back to the end of the tour with `tour .`.
+Current tour can be listed with `tour ls` and scrubbed with `tour clear`."""
+        # Creating the tour list if needed
+        self.get_list("tour") 
+        line = line.strip()
+        if not line:
+            # Fly to next waypoint on tour
+            if len(self.list_get_links("tour")) < 1:
+                print("End of tour.")
+            else:
+                url = self.list_go_to_line("1","tour")
+                if url:
+                    self.list_rm_url(url,"tour")
+        elif line == "ls":
+            self.list_show("tour")
+        elif line == "clear":
+            for l in self.list_get_links("tour"):
+                self.list_rm_url(l.url_mode(),"tour")
+        elif line == "*":
+            for l in self.gi.get_links():
+                self.list_add_line("tour",gi=l,verbose=False)
+        elif line == ".":
+            self.list_add_line("tour",verbose=False)
+        elif looks_like_url(line):
+            self.list_add_line("tour",gi=GeminiItem(line))
+        elif line in self.list_lists():
+            list_path = self.list_path(line)
+            if not list_path:
+                print("List %s does not exist. Cannot add it to tour"%(list))
+            else:
+                gi = GeminiItem("list:///%s"%line)
+                display = not self.sync_only
+                if gi:
+                    for l in gi.get_links():
+                        self.list_add_line("tour",gi=l,verbose=False)
+        else:
+            for index in line.split():
+                try:
+                    pair = index.split('-')
+                    if len(pair) == 1:
+                        # Just a single index
+                        n = int(index)
+                        gi = self.gi.get_link(n)
+                        self.list_add_line("tour",gi=gi,verbose=False)
+                    elif len(pair) == 2:
+                        # Two endpoints for a range of indices
+                        if int(pair[0]) < int(pair[1]):
+                            for n in range(int(pair[0]), int(pair[1]) + 1):
+                                gi = self.gi.get_link(n)
+                                self.list_add_line("tour",gi=gi,verbose=False)
+                        else:
+                            for n in range(int(pair[0]), int(pair[1]) - 1, -1):
+                                gi = self.gi.get_link(n)
+                                self.list_add_line("tour",gi=gi,verbose=False)
+
+                    else:
+                        # Syntax error
+                        print("Invalid use of range syntax %s, skipping" % index)
+                except ValueError:
+                    print("Non-numeric index %s, skipping." % index)
+                except IndexError:
+                    print("Invalid index %d, skipping." % n)
+
+    @needs_gi
+    def do_mark(self, line):
+        """Mark the current item with a single letter.  This letter can then
+be passed to the 'go' command to return to the current item later.
+Think of it like marks in vi: 'mark a'='ma' and 'go a'=''a'.
+Marks are temporary until shutdown (not saved to disk)."""
+        line = line.strip()
+        if not line:
+            for mark, gi in self.marks.items():
+                print("[%s] %s (%s)" % (mark, gi.name, gi.url))
+        elif line.isalpha() and len(line) == 1:
+            self.marks[line] = self.gi
+        else:
+            print("Invalid mark, must be one letter")
+    
+    @needs_gi
+    def do_info(self,line):
+        """Display information about current page."""
+        out = self.gi.get_page_title() + "\n\n"
+        out += "URL      :   " + self.gi.url + "\n"
+        out += "Path     :   " + self.gi.path + "\n"
+        out += "Mime     :   " + self.gi.get_mime() + "\n"
+        out += "Cache    :   " + self.gi.get_cache_path() + "\n"
+        tmp = self.gi.get_temp_filename()
+        if tmp != self.gi.get_cache_path():
+            out += "Tempfile :   " + self.gi.get_temp_filename() + "\n"
+        if self.gi.renderer :
+            rend = str(self.gi.renderer.__class__)
+            rend = rend.lstrip("")
+        else:
+            rend = "None"
+        out += "Renderer :   " + rend + "\n\n"
+        lists = []
+        for l in self.list_lists():
+            if self.list_has_url(self.gi.url,l):
+                lists.append(l)
+        if len(lists) > 0:
+            out += "Page appeard in following lists :\n"
+            for l in lists:
+                if not self.list_is_system(l):
+                    status = "normal list"
+                    if self.list_is_subscribed(l):
+                        status = "subscription"
+                    elif self.list_is_frozen(l):
+                        status = "frozen list"
+                    out += " • %s\t(%s)\n" %(l,status)
+            for l in lists:
+                if self.list_is_system(l):
+                    out += " • %s\n" %l
+        else:
+            out += "Page is not save in any list"
+        print(out)
+
+    def do_version(self, line):
+        """Display version and system information."""
+        def has(value):
+            if value:
+                return "\t\x1b[1;32mInstalled\x1b[0m\n"
+            else:
+                return "\t\x1b[1;31mNot Installed\x1b[0m\n"
+        output = "Offpunk " + _VERSION + "\n"
+        output += "===========\n"
+        output += "Highly recommended:\n"
+        output += " - python-cryptography : " + has(_HAS_CRYPTOGRAPHY)
+        output += " - xdg-open            : " + has(_HAS_XDGOPEN)
+        output += "\nWeb browsing:\n"
+        output += " - python-requests     : " + has(_DO_HTTP)
+        output += " - python-feedparser   : " + has(_DO_FEED)
+        output += " - python-bs4          : " + has(_HAS_SOUP)
+        output += " - python-readability  : " + has(_HAS_READABILITY)
+        output += " - timg 1.3.2+         : " + has(_NEW_TIMG)
+        if _NEW_CHAFA:
+            output += " - chafa 1.10+         : " + has(_HAS_CHAFA)
+        else:
+            output += " - chafa               : " + has(_HAS_CHAFA)
+            output += " - python-pil          : " + has(_HAS_PIL)
+        output += "\nNice to have:\n"
+        output += " - python-setproctitle : " + has(_HAS_SETPROCTITLE)
+        output += " - xsel                : " + has(_HAS_XSEL)
+
+        output += "\nFeatures :\n"
+        if _NEW_CHAFA:
+            output += " - Render images (chafa or timg)              : " + has(_RENDER_IMAGE)
+        else:
+            output += " - Render images (python-pil, chafa or timg)  : " + has(_RENDER_IMAGE)
+        output += " - Render HTML (bs4, readability)             : " + has(_DO_HTML)
+        output += " - Render Atom/RSS feeds (feedparser)         : " + has(_DO_FEED)
+        output += " - Connect to http/https (requests)           : " + has(_DO_HTTP)
+        output += " - copy to/from clipboard (xsel)              : " + has(_HAS_XSEL)
+        output += " - restore last position (less 572+)          : " + has(_LESS_RESTORE_POSITION) 
+        output += "\n"
+        output += "Config directory    : " +  _CONFIG_DIR + "\n"
+        output += "User Data directory : " +  _DATA_DIR + "\n"
+        output += "Cache directoy      : " +  _CACHE_PATH
+
+        print(output)
+
+    ### Stuff that modifies the lookup table
+    def do_ls(self, line):
+        """List contents of current index.
+Use 'ls -l' to see URLs."""
+        self._show_lookup(url = "-l" in line)
+        self.page_index = 0
+
+    def do_search(self,line):
+        """Search on Gemini using the engine configured (by default kennedy.gemi.dev)
+        You can configure it using "set search URL".
+        URL should contains one "%s" that will be replaced by the search term."""
+        search = urllib.parse.quote(line)
+        url = self.options["search"]%search
+        gi = GeminiItem(url)
+        self._go_to_gi(gi)
+
+    def do_wikipedia(self,line):
+        """Search on wikipedia using the configured Gemini interface.
+        The first word should be the two letters code for the language.
+        Exemple : "wikipedia en Gemini protocol"
+        But you can also use abbreviations to go faster:
+        "wen Gemini protocol". (your abbreviation might be missing, report the bug)
+        The interface used can be modified with the command:
+        "set wikipedia URL" where URL should contains two "%s", the first
+        one used for the language, the second for the search string."""
+        words = line.split(" ",maxsplit=1)
+        if len(words[0]) == 2:
+            lang = words[0]
+            search = urllib.parse.quote(words[1])
+        else:
+            lang = "en"
+            search = urllib.parse.quote(line)
+        url = self.options["wikipedia"]%(lang,search)
+        gi = GeminiItem(url)
+        self._go_to_gi(gi)
+
+    def do_gus(self, line):
+        """Submit a search query to the geminispace.info search engine."""
+        gus = GeminiItem("gemini://geminispace.info/search")
+        self._go_to_gi(gus.query(line))
+
+    def do_history(self, *args):
+        """Display history."""
+        self.list_show("history")
+
+    @needs_gi
+    def do_find(self, searchterm):
+        """Find in current page by displaying only relevant lines (grep)."""
+        self.gi.display(grep=searchterm) 
+
+    def emptyline(self):
+        """Page through index ten lines at a time."""
+        i = self.page_index
+        if not self.gi or i > len(self.gi.get_links()):
+            return
+        self._show_lookup(offset=i, end=i+10)
+        self.page_index += 10
+
+    ### Stuff that does something to most recently viewed item
+    @needs_gi
+    def do_cat(self, *args):
+        """Run most recently visited item through "cat" command."""
+        run("cat", input=open(self.gi.get_temp_filename(), "rb"), direct_output=True)
+
+    @needs_gi
+    def do_view(self, *args):
+        """Run most recently visited item through "less" command, restoring \
+previous position.
+Use "view normal" to see the default article view on html page.
+Use "view full" to see a complete html page instead of the article view.
+Use "view feed" to see the the linked feed of the page (in any).
+Use "view feeds" to see available feeds on this page.
+(full, feed, feeds have no effect on non-html content)."""
+        if self.gi and args and args[0] != "":
+            if args[0] in ["full","debug"]:
+                self._go_to_gi(self.gi,mode=args[0])
+            elif args[0] in ["normal","readable"]:
+                self._go_to_gi(self.gi,mode="readable")
+            elif args[0] == "feed":
+                subs = self.gi.get_subscribe_links()
+                if len(subs) > 1:
+                    self.do_go(subs[1][0])
+                elif "rss" in subs[0][1] or "atom" in subs[0][1]:
+                    print("%s is already a feed" %self.gi.url)
+                else:
+                    print("No other feed found on %s"%self.gi.url)
+            elif args[0] == "feeds":
+                subs = self.gi.get_subscribe_links()
+                stri = "Available views :\n" 
+                counter = 0
+                for s in subs:
+                    counter += 1
+                    stri += "[%s] %s [%s]\n"%(counter,s[0],s[1])
+                stri += "Which view do you want to see ? >"
+                ans = input(stri)
+                if ans.isdigit() and 0 < int(ans) <= len(subs):
+                    self.do_go(subs[int(ans)-1][0])
+            else:
+                print("Valid argument for view are : normal, full, feed, feeds")
+        else:
+            self._go_to_gi(self.gi)
+                
+    @needs_gi
+    def do_open(self, *args):
+        """Open current item with the configured handler or xdg-open.
+Uses "open url" to open current URL in a browser.
+see "handler" command to set your handler."""
+        if args[0] == "url":
+            run("xdg-open %s", parameter=self.gi.url, direct_output=True)
+        else:
+            cmd_str = self._get_handler_cmd(self.gi.get_mime())
+            run(cmd_str, parameter=self.gi.get_body(as_file=True), direct_output=True)
+
+    @needs_gi
+    def do_shell(self, line):
+        """'cat' most recently visited item through a shell pipeline.
+'!' is an useful shortcut."""
+        run(line, input=open(self.gi.get_temp_filename(), "rb"), direct_output=True)
+
+    @needs_gi
+    def do_save(self, line):
+        """Save an item to the filesystem.
+'save n filename' saves menu item n to the specified filename.
+'save filename' saves the last viewed item to the specified filename.
+'save n' saves menu item n to an automagic filename."""
+        args = line.strip().split()
+
+        # First things first, figure out what our arguments are
+        if len(args) == 0:
+            # No arguments given at all
+            # Save current item, if there is one, to a file whose name is
+            # inferred from the gemini path
+            if not self.gi.is_cache_valid():
+                print("You cannot save if not cached!")
+                return
+            else:
+                index = None
+                filename = None
+        elif len(args) == 1:
+            # One argument given
+            # If it's numeric, treat it as an index, and infer the filename
+            try:
+                index = int(args[0])
+                filename = None
+            # If it's not numeric, treat it as a filename and
+            # save the current item
+            except ValueError:
+                index = None
+                filename = os.path.expanduser(args[0])
+        elif len(args) == 2:
+            # Two arguments given
+            # Treat first as an index and second as filename
+            index, filename = args
+            try:
+                index = int(index)
+            except ValueError:
+                print("First argument is not a valid item index!")
+                return
+            filename = os.path.expanduser(filename)
+        else:
+            print("You must provide an index, a filename, or both.")
+            return
+
+        # Next, fetch the item to save, if it's not the current one.
+        if index:
+            last_gi = self.gi
+            try:
+                gi = self.gi.get_link(index)
+                self._go_to_gi(gi, update_hist = False, handle = False)
+            except IndexError:
+                print ("Index too high!")
+                self.gi = last_gi
+                return
+        else:
+            gi = self.gi
+
+        # Derive filename from current GI's path, if one hasn't been set
+        if not filename:
+            filename = gi.get_filename()
+        # Check for filename collisions and actually do the save if safe
+        if os.path.exists(filename):
+            print("File %s already exists!" % filename)
+        else:
+            # Don't use _get_active_tmpfile() here, because we want to save the
+            # "source code" of menus, not the rendered view - this way Offpunk
+            # can navigate to it later.
+            path = gi.get_body(as_file=True)
+            if os.path.isdir(path):
+                print("Can’t save %s because it’s a folder, not a file"%path)
+            else:
+                print("Saved to %s" % filename)
+                shutil.copyfile(path, filename)
+
+        # Restore gi if necessary
+        if index != None:
+            self._go_to_gi(last_gi, handle=False)
+
+    @needs_gi
+    def do_url(self, *args):
+        """Print URL of most recently visited item."""
+        print(self.gi.url)
+
+    ### Bookmarking stuff
+    @needs_gi
+    def do_add(self, line):
+        """Add the current URL to the list specied as argument.
+If no argument given, URL is added to Bookmarks."""
+        args = line.split()
+        if len(args) < 1 :
+            list = "bookmarks"
+            if not self.list_path(list):
+                self.list_create(list)
+            self.list_add_line(list)
+        else:
+            self.list_add_line(args[0])
+    
+    # Get the list file name, creating or migrating it if needed.
+    # Migrate bookmarks/tour/to_fetch from XDG_CONFIG to XDG_DATA
+    # We migrate only if the file exists in XDG_CONFIG and not XDG_DATA
+    def get_list(self,list):
+        list_path = self.list_path(list)
+        if not list_path:
+            old_file_gmi = os.path.join(_CONFIG_DIR,list + ".gmi")
+            old_file_nogmi = os.path.join(_CONFIG_DIR,list)
+            target = os.path.join(_DATA_DIR,"lists")
+            if os.path.exists(old_file_gmi):
+                shutil.move(old_file_gmi,target)
+            elif os.path.exists(old_file_nogmi):
+                targetgmi = os.path.join(target,list+".gmi")
+                shutil.move(old_file_nogmi,targetgmi)
+            else:
+                if list == "subscribed":
+                    title = "Subscriptions #subscribed (new links in those pages will be added to tour)"
+                elif list == "to_fetch":
+                    title = "Links requested and to be fetched during the next --sync"
+                else:
+                    title = None
+                self.list_create(list, title=title,quite=True)
+                list_path = self.list_path(list)
+        return list_path
+    
+    @needs_gi
+    def do_subscribe(self,line):
+        """Subscribe to current page by saving it in the "subscribed" list.
+If a new link is found in the page during a --sync, the new link is automatically
+fetched and added to your next tour.
+To unsubscribe, remove the page from the "subscribed" list."""
+        subs = self.gi.get_subscribe_links()
+        if len(subs) > 1:
+            stri = "Multiple feeds have been found :\n"
+        elif "rss" in subs[0][1] or "atom" in subs[0][1] :
+            stri = "This page is already a feed:\n"
+        else:
+            stri = "No feed detected. You can still watch the page :\n"
+        counter = 0
+        for l in subs:
+            link = l[0]
+            already = []
+            for li in self.list_lists():
+                if self.list_is_subscribed(li):
+                    if self.list_has_url(link,li):
+                        already.append(li)
+            stri += "[%s] %s [%s]\n"%(counter+1,link,l[1])
+            if len(already) > 0:
+                stri += "\t -> (already subscribed through lists %s)\n"%(str(already))
+            counter += 1
+        stri += "\n"
+        stri += "Which feed do you want to subscribe ? > "
+        ans = input(stri)
+        if ans.isdigit() and 0 < int(ans) <= len(subs):
+            sublink,mime,title = subs[int(ans)-1]
+        else:
+            sublink,title = None,None
+        if sublink:
+            sublink = self.gi.absolutise_url(sublink)
+            gi = GeminiItem(sublink,name=title)
+            list_path = self.get_list("subscribed")
+            added = self.list_add_line("subscribed",gi=gi,verbose=False)
+            if added :
+                print("Subscribed to %s" %sublink)
+            else:
+                print("You are already subscribed to %s"%sublink)
+        else:
+            print("No subscription registered")
+
+    def do_bookmarks(self, line):
+        """Show or access the bookmarks menu.
+'bookmarks' shows all bookmarks.
+'bookmarks n' navigates immediately to item n in the bookmark menu.
+Bookmarks are stored using the 'add' command."""
+        list_path = self.get_list("bookmarks")
+        args = line.strip()
+        if len(args.split()) > 1 or (args and not args.isnumeric()):
+            print("bookmarks command takes a single integer argument!")
+        elif args:
+            self.list_go_to_line(args,"bookmarks")
+        else:
+            self.list_show("bookmarks")
+    
+    def do_archive(self,args):
+        """Archive current page by removing it from every list and adding it to
+archives, which is a special historical list limited in size. It is similar to `move archives`."""
+        for li in self.list_lists():
+            if li not in ["archives", "history"]:
+                deleted = self.list_rm_url(self.gi.url_mode(),li)
+                if deleted:
+                    print("Removed from %s"%li)
+        self.list_add_top("archives",limit=self.options["archives_size"])
+        print("Archiving: %s"%self.gi.get_page_title())
+        print("\x1b[2;34mCurrent maximum size of archives : %s\x1b[0m" %self.options["archives_size"])
+
+    def list_add_line(self,list,gi=None,verbose=True):
+        list_path = self.list_path(list)
+        if not list_path and self.list_is_system(list):
+            self.list_create(list,quite=True)
+            list_path = self.list_path(list)
+        if not list_path:
+            print("List %s does not exist. Create it with ""list create %s"""%(list,list))
+            return False
+        else:
+            if not gi:
+                gi = self.gi
+            # first we check if url already exists in the file
+            with open(list_path,"r") as l_file:
+                lines = l_file.readlines()
+                l_file.close()
+                for l in lines:
+                    sp = l.split()
+                    if gi.url_mode() in sp:
+                        if verbose:
+                            print("%s already in %s."%(gi.url,list))
+                        return False
+            with open(list_path,"a") as l_file:
+                l_file.write(gi.to_map_line())
+                l_file.close()
+            if verbose:
+                print("%s added to %s" %(gi.url,list))
+            return True
+    
+    def list_add_top(self,list,limit=0,truncate_lines=0):
+        if not self.gi:
+            return
+        stri = self.gi.to_map_line().strip("\n")
+        if list == "archives":
+            stri += ", archived on "
+        elif list == "history":
+            stri += ", visited on "
+        else:
+            stri += ", added to %s on "%list
+        stri += time.ctime() + "\n"
+        list_path = self.get_list(list)
+        with open(list_path,"r") as l_file:
+            lines = l_file.readlines()
+            l_file.close()
+        with open(list_path,"w") as l_file:
+            l_file.write("#%s\n"%list)
+            l_file.write(stri)
+            counter = 0
+            # Truncating is useful in case we open a new branch
+            # after a few back in history
+            to_truncate = truncate_lines
+            for l in lines:
+                if not l.startswith("#"):
+                    if to_truncate > 0:
+                        to_truncate -= 1
+                    elif limit == 0 or counter < limit:
+                        l_file.write(l)
+                        counter += 1
+            l_file.close()
+
+
+    # remove an url from a list.
+    # return True if the URL was removed
+    # return False if the URL was not found
+    def list_rm_url(self,url,list):
+        return self.list_has_url(url,list,deletion=True)
+   
+    # deletion and has_url are so similar, I made them the same method
+    def list_has_url(self,url,list,deletion=False):
+        list_path = self.list_path(list)
+        if list_path:
+            to_return = False
+            with open(list_path,"r") as lf:
+                lines = lf.readlines()
+                lf.close()
+            to_write = []
+            # let’s remove the mode
+            url = url.split("##offpunk_mode=")[0]
+            for l in lines:
+                # we separate components of the line
+                # to ensure we identify a complete URL, not a part of it
+                splitted = l.split()
+                if url not in splitted and len(splitted) > 1:
+                    current = splitted[1].split("##offpunk_mode=")[0]
+                    #sometimes, we must remove the ending "/"
+                    if url == current:
+                        to_return = True
+                    elif url.endswith("/") and url[:-1] == current:
+                        to_return = True
+                    else:
+                        to_write.append(l)
+                else:
+                    to_return = True
+            if deletion :
+                with open(list_path,"w") as lf:
+                    for l in to_write:
+                        lf.write(l)
+                    lf.close()
+            return to_return
+        else:
+            return False
+
+    def list_get_links(self,list):
+        list_path = self.list_path(list)
+        if list_path:
+            gi = GeminiItem("list:///%s"%list)
+            return gi.get_links()
+        else:
+            return []
+
+    def list_go_to_line(self,line,list):
+        list_path = self.list_path(list)
+        if not list_path:
+            print("List %s does not exist. Create it with ""list create %s"""%(list,list))
+        elif not line.isnumeric():
+            print("go_to_line requires a number as parameter")
+        else:
+            gi = GeminiItem("list:///%s"%list)
+            gi = gi.get_link(int(line))
+            display = not self.sync_only
+            if gi:
+                self._go_to_gi(gi,handle=display)
+                return gi.url_mode()
+
+    def list_show(self,list):
+        list_path = self.list_path(list)
+        if not list_path:
+            print("List %s does not exist. Create it with ""list create %s"""%(list,list))
+        else:
+            gi = GeminiItem("list:///%s"%list)
+            display = not self.sync_only 
+            self._go_to_gi(gi,handle=display)
+
+    #return the path of the list file if list exists.
+    #return None if the list doesn’t exist.
+    def list_path(self,list):
+        listdir = os.path.join(_DATA_DIR,"lists")
+        list_path = os.path.join(listdir, "%s.gmi"%list)
+        if os.path.exists(list_path):
+            return list_path
+        else:
+            return None
+
+    def list_create(self,list,title=None,quite=False):
+        list_path = self.list_path(list)
+        if list in ["create","edit","delete","help"]:
+            print("%s is not allowed as a name for a list"%list)
+        elif not list_path:
+            listdir = os.path.join(_DATA_DIR,"lists")
+            os.makedirs(listdir,exist_ok=True)
+            list_path = os.path.join(listdir, "%s.gmi"%list)
+            with open(list_path,"a") as lfile:
+                if title:
+                    lfile.write("# %s\n"%title)
+                else:
+                    lfile.write("# %s\n"%list)
+                lfile.close()
+            if not quite:
+                print("list created. Display with `list %s`"%list)
+        else:
+            print("list %s already exists" %list)
+   
+    def do_move(self,arg):
+        """move LIST will add the current page to the list LIST.
+With a major twist: current page will be removed from all other lists. 
+If current page was not in a list, this command is similar to `add LIST`."""
+        if not arg:
+            print("LIST argument is required as the target for your move")
+        elif arg[0] == "archives":
+            self.do_archive()
+        else:
+            args = arg.split()
+            list_path = self.list_path(args[0])
+            if not list_path:
+                print("%s is not a list, aborting the move" %args[0])
+            else:
+                lists = self.list_lists()
+                for l in lists:
+                    if l != args[0] and l not in ["archives", "history"]:
+                        isremoved = self.list_rm_url(self.gi.url_mode(),l)
+                        if isremoved:
+                            print("Removed from %s"%l)
+                self.list_add_line(args[0])
+    
+    def list_lists(self):
+        listdir = os.path.join(_DATA_DIR,"lists")
+        to_return = []
+        if os.path.exists(listdir):
+            lists = os.listdir(listdir)
+            if len(lists) > 0:
+                for l in lists:
+                    #removing the .gmi at the end of the name
+                    to_return.append(l[:-4])
+        return to_return
+    
+    def list_has_status(self,list,status):
+        path = self.list_path(list)
+        toreturn = False
+        if path:
+            with open(path) as f:
+                line = f.readline().strip()
+                f.close()
+            if line.startswith("#") and status in line:
+                toreturn = True
+        return toreturn
+
+    def list_is_subscribed(self,list):
+        return self.list_has_status(list,"#subscribed")
+    def list_is_frozen(self,list):
+        return self.list_has_status(list,"#frozen")
+    def list_is_system(self,list):
+        return list in ["history","to_fetch","archives","tour"]
+
+    # This modify the status of a list to one of :
+    # normal, frozen, subscribed
+    # action is either #frozen, #subscribed or None
+    def list_modify(self,list,action=None):
+        path = self.list_path(list)
+        with open(path) as f:
+            lines = f.readlines()
+            f.close()
+        if lines[0].strip().startswith("#"):
+            first_line = lines.pop(0).strip("\n")
+        else:
+            first_line = "# %s "%list
+        first_line = first_line.replace("#subscribed","").replace("#frozen","")
+        if action:
+            first_line += " " + action
+            print("List %s has been marked as %s"%(list,action))
+        else:
+            print("List %s is now a normal list" %list)
+        first_line += "\n"
+        lines.insert(0,first_line)
+        with open(path,"w") as f:
+            for line in lines:
+                f.write(line)
+            f.close()
+    def do_list(self,arg):
+        """Manage list of bookmarked pages.
+- list : display available lists
+- list $LIST : display pages in $LIST
+- list create $NEWLIST : create a new list
+- list edit $LIST : edit the list
+- list subscribe $LIST : during sync, add new links found in listed pages to tour 
+- list freeze $LIST : don’t update pages in list during sync if a cache already exists
+- list normal $LIST : update pages in list during sync but don’t add anything to tour
+- list delete $LIST : delete a list permanently (a confirmation is required)
+- list help : print this help
+See also :
+- add $LIST (to add current page to $LIST or, by default, to bookmarks)
+- move $LIST (to add current page to list while removing from all others)
+- archive (to remove current page from all lists while adding to archives)
+Note: There’s no "delete" on purpose. The use of "archive" is recommended."""
+        listdir = os.path.join(_DATA_DIR,"lists")
+        os.makedirs(listdir,exist_ok=True)
+        if not arg:
+            lists = self.list_lists()
+            if len(lists) > 0:
+                lgi = GeminiItem("list:///")
+                self._go_to_gi(lgi)
+            else:
+                print("No lists yet. Use `list create`")
+        else:
+            args = arg.split()
+            if args[0] == "create":
+                if len(args) > 2:
+                    name = " ".join(args[2:])
+                    self.list_create(args[1].lower(),title=name)
+                elif len(args) == 2:
+                    self.list_create(args[1].lower())
+                else:
+                    print("A name is required to create a new list. Use `list create NAME`")
+            elif args[0] == "edit":
+                editor = None
+                if "editor" in self.options and self.options["editor"]:
+                    editor = self.options["editor"]
+                elif os.environ.get("VISUAL"):
+                    editor = os.environ.get("VISUAL")
+                elif os.environ.get("EDITOR"):
+                    editor = os.environ.get("EDITOR")
+                if editor:
+                    if len(args) > 1 and args[1] in self.list_lists():
+                        path = os.path.join(listdir,args[1]+".gmi")
+                        try:
+                            # Note that we intentionally don't quote the editor.
+                            # In the unlikely case `editor` includes a percent
+                            # sign, we also escape it for the %-formatting.
+                            cmd = editor.replace("%", "%%") + " %s"
+                            run(cmd, parameter=path, direct_output=True)
+                        except Exception as err:
+                            print(err)
+                            print("Please set a valid editor with \"set editor\"")
+                    else:
+                        print("A valid list name is required to edit a list")
+                else:
+                    print("No valid editor has been found.")
+                    print("You can use the following command to set your favourite editor:")
+                    print("set editor EDITOR")
+                    print("or use the $VISUAL or $EDITOR environment variables.")
+            elif args[0] == "delete":
+                if len(args) > 1:
+                    if self.list_is_system(args[1]):
+                        print("%s is a system list which cannot be deleted"%args[1])
+                    elif args[1] in self.list_lists():
+                        size = len(self.list_get_links(args[1]))
+                        stri = "Are you sure you want to delete %s ?\n"%args[1]
+                        confirm = "YES"
+                        if size > 0:
+                            stri += "! %s items in the list will be lost !\n"%size
+                            confirm = "YES DELETE %s" %size
+                        else :
+                            stri += "The list is empty, it should be safe to delete it.\n"
+                        stri += "Type \"%s\" (in capital, without quotes) to confirm :"%confirm
+                        answer = input(stri)
+                        if answer == confirm:
+                            path = os.path.join(listdir,args[1]+".gmi")
+                            os.remove(path)
+                            print("* * * %s has been deleted" %args[1])
+                    else:
+                        print("A valid list name is required to be deleted")
+                else:
+                    print("A valid list name is required to be deleted")
+            elif args[0] in ["subscribe","freeze","normal"]:
+                if len(args) > 1:
+                    if self.list_is_system(args[1]):
+                        print("You cannot modify %s which is a system list"%args[1])
+                    elif args[1] in self.list_lists():
+                        if args[0] == "subscribe":
+                            action = "#subscribed"
+                        elif args[0] == "freeze":
+                            action = "#frozen"
+                        else:
+                            action = None
+                        self.list_modify(args[1],action=action)
+                else:
+                    print("A valid list name is required after %s" %args[0])
+            elif args[0] == "help":
+                self.onecmd("help list")
+            elif len(args) == 1:
+                self.list_show(args[0].lower())
+            else:
+                self.list_go_to_line(args[1],args[0].lower())
+
+    def do_help(self, arg):
+        """ALARM! Recursion detected! ALARM! Prepare to eject!"""
+        if arg == "!":
+            print("! is an alias for 'shell'")
+        elif arg == "?":
+            print("? is an alias for 'help'")
+        elif arg in _ABBREVS:
+            full_cmd = _ABBREVS[arg]
+            print("%s is an alias for '%s'" %(arg,full_cmd))
+            print("See the list of aliases with 'abbrevs'")
+            print("'help %s':"%full_cmd)
+            cmd.Cmd.do_help(self, full_cmd)
+        else:
+            cmd.Cmd.do_help(self, arg)
+
+    ### Flight recorder
+    def do_blackbox(self, *args):
+        """Display contents of flight recorder, showing statistics for the
+current gemini browsing session."""
+        lines = []
+        # Compute flight time
+        now = time.time()
+        delta = now - self.log["start_time"]
+        hours, remainder = divmod(delta, 3600)
+        minutes, seconds = divmod(remainder, 60)
+        # Count hosts
+        ipv4_hosts = len([host for host in self.visited_hosts if host[0] == socket.AF_INET])
+        ipv6_hosts = len([host for host in self.visited_hosts if host[0] == socket.AF_INET6])
+        # Assemble lines
+        lines.append(("Patrol duration", "%02d:%02d:%02d" % (hours, minutes, seconds)))
+        lines.append(("Requests sent:", self.log["requests"]))
+        lines.append(("   IPv4 requests:", self.log["ipv4_requests"]))
+        lines.append(("   IPv6 requests:", self.log["ipv6_requests"]))
+        lines.append(("Bytes received:", self.log["bytes_recvd"]))
+        lines.append(("   IPv4 bytes:", self.log["ipv4_bytes_recvd"]))
+        lines.append(("   IPv6 bytes:", self.log["ipv6_bytes_recvd"]))
+        lines.append(("Unique hosts visited:", len(self.visited_hosts)))
+        lines.append(("   IPv4 hosts:", ipv4_hosts))
+        lines.append(("   IPv6 hosts:", ipv6_hosts))
+        lines.append(("DNS failures:", self.log["dns_failures"]))
+        lines.append(("Timeouts:", self.log["timeouts"]))
+        lines.append(("Refused connections:", self.log["refused_connections"]))
+        lines.append(("Reset connections:", self.log["reset_connections"]))
+        lines.append(("Cache hits:", self.log["cache_hits"]))
+        # Print
+        for key, value in lines:
+            print(key.ljust(24) + str(value).rjust(8))
+
+    
+    def do_sync(self, line):
+        """Synchronize all bookmarks lists.
+- New elements in pages in subscribed lists will be added to tour
+- Elements in list to_fetch will be retrieved and added to tour
+- Normal lists will be synchronized and updated
+- Frozen lists will be fetched only if not present.
+
+Argument : duration of cache validity (in seconds)."""
+        if self.offline_only:
+            print("Sync can only be achieved online. Change status with `online`.")
+            return
+        args = line.split()
+        if len(args) > 0:
+            if not args[0].isdigit():
+                print("sync argument should be the cache validity expressed in seconds")
+                return
+            else:
+                validity = int(args[0])
+        else:
+            validity = 0
+        self.call_sync(refresh_time=validity)
+
+    def call_sync(self,refresh_time=0,depth=1):
+        # fetch_gitem is the core of the sync algorithm.
+        # It takes as input :
+        # - a GeminiItem to be fetched
+        # - depth : the degree of recursion to build the cache (0 means no recursion)
+        # - validity : the age, in seconds, existing caches need to have before
+        #               being refreshed (0 = never refreshed if it already exists)
+        # - savetotour : if True, newly cached items are added to tour
+        def add_to_tour(gitem):
+            if gitem and gitem.is_cache_valid():
+                toprint = "  -> adding to tour: %s" %gitem.url
+                width = term_width() - 1
+                toprint = toprint[:width]
+                toprint += " "*(width-len(toprint))
+                print(toprint)
+                self.list_add_line("tour",gi=gitem,verbose=False)
+                return True
+            else:
+                return False
+        def fetch_gitem(gitem,depth=0,validity=0,savetotour=False,count=[0,0],strin=""):
+            #savetotour = True will save to tour newly cached content
+            # else, do not save to tour
+            #regardless of valitidy
+            if not gitem: return
+            if not gitem.is_cache_valid(validity=validity):
+                if strin != "":
+                    endline = '\r'
+                else:
+                    endline = None
+                #Did we already had a cache (even an old one) ?
+                isnew = not gitem.is_cache_valid()
+                toprint = "%s [%s/%s] Fetch "%(strin,count[0],count[1]) + gitem.url
+                width = term_width() - 1
+                toprint = toprint[:width]
+                toprint += " "*(width-len(toprint))
+                print(toprint,end=endline)
+                #If not saving to tour, then we should limit download size
+                limit = not savetotour
+                self._go_to_gi(gitem,update_hist=False,limit_size=limit)
+                if savetotour and isnew and gitem.is_cache_valid():
+                    #we add to the next tour only if we managed to cache 
+                    #the ressource
+                    add_to_tour(gitem)
+            #Now, recursive call, even if we didn’t refresh the cache
+            # This recursive call is impacting performances a lot but is needed 
+            # For the case when you add a address to a list to read later
+            # You then expect the links to be loaded during next refresh, even
+            # if the link itself is fresh enough
+            # see fetch_list()
+            if depth > 0:
+                #we should only savetotour at the first level of recursion
+                # The code for this was removed so, currently, we savetotour
+                # at every level of recursion.
+                links = gitem.get_links(mode="links_only")
+                subcount = [0,len(links)]
+                d = depth - 1
+                for k in links:
+                    #recursive call (validity is always 0 in recursion)
+                    substri = strin + " -->"
+                    subcount[0] += 1
+                    fetch_gitem(k,depth=d,validity=0,savetotour=savetotour,\
+                                        count=subcount,strin=substri) 
+        def fetch_list(list,validity=0,depth=1,tourandremove=False,tourchildren=False):
+            links = self.list_get_links(list)
+            end = len(links)
+            counter = 0
+            print(" * * * %s to fetch in %s * * *" %(end,list))
+            for l in links:
+                counter += 1
+                # If cache for a link is newer than the list
+                fetch_gitem(l,depth=depth,validity=validity,savetotour=tourchildren,count=[counter,end])
+                if tourandremove:
+                    if add_to_tour(l):
+                        self.list_rm_url(l.url_mode(),list)
+            
+        self.sync_only = True
+        lists = self.list_lists()
+        # We will fetch all the lists except "archives" and "history"
+        # We keep tour for the last round
+        subscriptions = []
+        normal_lists = []
+        fridge = []
+        for l in lists:
+            if not self.list_is_system(l):
+                if self.list_is_frozen(l):
+                    fridge.append(l)
+                elif self.list_is_subscribed(l):
+                    subscriptions.append(l)
+                else:
+                    normal_lists.append(l)
+        # We start with the "subscribed" as we need to find new items
+        starttime = int(time.time())
+        for l in subscriptions:
+            fetch_list(l,validity=refresh_time,depth=depth,tourchildren=True)
+        #Then the fetch list (item are removed from the list after fetch)
+        # We fetch regarless of the refresh_time
+        if "to_fetch" in lists:
+            nowtime = int(time.time())
+            short_valid = nowtime - starttime
+            fetch_list("to_fetch",validity=short_valid,depth=depth,tourandremove=True)
+        #then we fetch all the rest (including bookmarks and tour)
+        for l in normal_lists:
+            fetch_list(l,validity=refresh_time,depth=depth)
+        for l in fridge:
+            fetch_list(l,validity=0,depth=depth)
+        #tour should be the last one as item my be added to it by others
+        fetch_list("tour",validity=refresh_time,depth=depth)
+        print("End of sync")
+        self.sync_only = False
+
+    ### The end!
+    def do_quit(self, *args):
+        """Exit Offpunk."""
+        def unlink(filename):
+            if filename and os.path.exists(filename):
+                os.unlink(filename)
+        # Close TOFU DB
+        self.db_conn.commit()
+        self.db_conn.close()
+        # Clean up after ourself
+
+        for cert in self.transient_certs_created:
+            for ext in (".crt", ".key"):
+                certfile = os.path.join(_CONFIG_DIR, "transient_certs", cert+ext)
+                if os.path.exists(certfile):
+                    os.remove(certfile)
+        print("You can close your screen!")
+        sys.exit()
+
+    do_exit = do_quit
+
+
+
+# Main function
+def main():
+
+    # Parse args
+    parser = argparse.ArgumentParser(description='A command line gemini client.')
+    parser.add_argument('--bookmarks', action='store_true',
+                        help='start with your list of bookmarks')
+    parser.add_argument('--tls-cert', metavar='FILE', help='TLS client certificate file')
+    parser.add_argument('--tls-key', metavar='FILE', help='TLS client certificate private key file')
+    parser.add_argument('--sync', action='store_true', 
+                        help='run non-interactively to build cache by exploring bookmarks')
+    parser.add_argument('--assume-yes', action='store_true', 
+                        help='assume-yes when asked questions about certificates/redirections during sync (lower security)')
+    parser.add_argument('--disable-http',action='store_true',
+                        help='do not try to get http(s) links (but already cached will be displayed)')
+    parser.add_argument('--fetch-later', action='store_true', 
+                        help='run non-interactively with an URL as argument to fetch it later')
+    parser.add_argument('--depth', 
+                        help='depth of the cache to build. Default is 1. More is crazy. Use at your own risks!')
+    parser.add_argument('--cache-validity', 
+                        help='duration for which a cache is valid before sync (seconds)')
+    parser.add_argument('--version', action='store_true',
+                        help='display version information and quit')
+    parser.add_argument('--features', action='store_true',
+                        help='display available features and dependancies then quit')
+    parser.add_argument('url', metavar='URL', nargs='*',
+                        help='start with this URL')
+    args = parser.parse_args()
+
+    # Handle --version
+    if args.version:
+        print("Offpunk " + _VERSION)
+        sys.exit()
+    elif args.features:
+        GeminiClient.do_version(None,None)
+        sys.exit() 
+    else:
+        for f in [_CONFIG_DIR, _CACHE_PATH, _DATA_DIR]:
+            if not os.path.exists(f):
+                print("Creating config directory {}".format(f))
+                os.makedirs(f)
+
+    # Instantiate client
+    gc = GeminiClient(synconly=args.sync)
+    torun_queue = []
+    
+    # Interactive if offpunk started normally
+    # False if started with --sync
+    # Queue is a list of command (potentially empty)
+    def read_config(queue,interactive=True):
+        rcfile = os.path.join(_CONFIG_DIR, "offpunkrc")
+        if os.path.exists(rcfile):
+            print("Using config %s" % rcfile)
+            with open(rcfile, "r") as fp:
+                for line in fp:
+                    line = line.strip()
+                    if ((args.bookmarks or args.url) and
+                        any((line.startswith(x) for x in ("go", "g", "tour", "t")))
+                        ):
+                        if args.bookmarks:
+                            print("Skipping rc command \"%s\" due to --bookmarks option." % line)
+                        else:
+                            print("Skipping rc command \"%s\" due to provided URLs." % line)
+                        continue
+                    # We always consider redirect
+                    # for the rest, we need to be interactive
+                    if line.startswith("redirect") or interactive:
+                        queue.append(line)
+        return queue
+    # Act on args
+    if args.tls_cert:
+        # If tls_key is None, python will attempt to load the key from tls_cert.
+        gc._activate_client_cert(args.tls_cert, args.tls_key)
+    if args.bookmarks:
+        torun_queue.append("bookmarks")
+    elif args.url:
+        if len(args.url) == 1:
+            torun_queue.append("go %s" % args.url[0])
+        else:
+            for url in args.url:
+                torun_queue.append("tour %s" % url)
+            torun_queue.append("tour")
+
+    if args.disable_http:
+        gc.support_http = False
+
+    # Endless interpret loop (except while --sync or --fetch-later)
+    if args.fetch_later:
+        if args.url:
+            gc.sync_only = True
+            for u in args.url:
+                gi = GeminiItem(u)
+                if gi and gi.is_cache_valid():
+                    gc.list_add_line("tour",gi)
+                else:
+                    gc.list_add_line("to_fetch",gi)
+        else:
+            print("--fetch-later requires an URL (or a list of URLS) as argument")
+    elif args.sync:
+        if args.assume_yes:
+            gc.automatic_choice = "y"
+            gc.onecmd("set accept_bad_ssl_certificates True")
+        if args.cache_validity:
+            refresh_time = int(args.cache_validity)
+        else:
+            # if no refresh time, a default of 0 is used (which means "infinite")
+            refresh_time = 0
+        if args.depth:
+            depth = int(args.depth)
+        else:
+            depth = 1
+        read_config(torun_queue, interactive=False)
+        for line in torun_queue:
+            gc.onecmd(line)
+        gc.call_sync(refresh_time=refresh_time,depth=depth)
+        gc.onecmd("blackbox")
+    else:
+        # We are in the normal mode. First process config file
+        torun_queue = read_config(torun_queue,interactive=True)
+        print("Welcome to Offpunk!")
+        print("Type `help` to get the list of available command.")
+        for line in torun_queue:
+            gc.onecmd(line)
+        
+        while True:
+            try:
+                gc.cmdloop()
+            except KeyboardInterrupt:
+                print("")
+
+if __name__ == '__main__':
+    main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..ba8298a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,8 @@
+cryptography
+requests
+feedparser
+bs4
+readability-lxml
+pillow
+setproctitle
+timg
diff --git a/screenshot_offpunk1.png b/screenshot_offpunk1.png
new file mode 100644
index 0000000000000000000000000000000000000000..16da2223de78e79c048e699670649cc6d1b232d3
GIT binary patch
literal 84970
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%0DR1@rw<`
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

?}4Vgz6s2M{?k7T2kmG6!bw@psZm%8n$di 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

$W~?`c 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 Ci#c@w literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f6cbba4 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +from setuptools import setup + +setup( + name='offpunk', + version='1.8', + description="Offline-First Gemini/Web/Gopher/RSS reader and browser", + author="Ploum", + author_email="offpunk@ploum.eu", + url='https://sr.ht/~lioploum/offpunk/', + classifiers=[ + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Communications', + 'Intended Audience :: End Users/Desktop', + 'Environment :: Console', + 'Development Status :: 4 - Beta', + ], + py_modules = ["offpunk"], + entry_points={ + "console_scripts": ["offpunk=offpunk:main"] + }, + install_requires=[], +) diff --git a/ubuntu_dependencies.txt b/ubuntu_dependencies.txt new file mode 100644 index 0000000..afdb976 --- /dev/null +++ b/ubuntu_dependencies.txt @@ -0,0 +1 @@ +sudo apt install less file xdg-utils xsel chafa timg python3-cryptography python3-requests python3-feedparser python3-bs4 python3-readability python3-pil python3-setproctitle