Introducing Offpunk

This commit is contained in:
Lionel Dricot 2021-12-30 16:03:08 +01:00
parent 3d66f99c95
commit 700cba7184
3 changed files with 79 additions and 63 deletions

View File

@ -1,7 +1,49 @@
# AV-98
# OFFPUNK
A command-line, text-based and offline Gemini browser.
Focused on Gemini first but with some Gopher/web support available or projected, the goal of Offpunk is to be able to synchronise your content once (a day, a week, a month) and then browse it while staying disconnected.
Offpunk is a fork of the original [AV-98](https://tildegit.org/solderpunk/AV-98) by Solderpunk and was originally only called AV-98-offline as an experimental branch.
## Lightning introduction
You use the `go` command to visit a URL, e.g. `go gemini.circumlunar.space`. If xsel is installed, go will automatically fetch the URL from your clipboard.
Links in Gemini documents are assigned numerical indices. Just type an index to
follow that link.
If a Gemini document is too long to fit on your screen, use the `less` command
to pipe it to the `less` pager.
Use `add` to add a capsule to your bookmarks and `bm` to show your bookmarks (which are stored in a file in you .config).
Use `offline` to only browse cached content and `online` to go back online.
Use the `help` command to learn about additional commands.
When launched with the "--sync" option, offpunk will run non-interactively and fetch content from your bookmarks and content tentatively accessed while offline. New content found in your bookmarks will be added 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.py --sync --cache-validity 43200`
will refresh your bookmarks if those are at least 12h old.
## TODO
Known issues in the code:
* WONTFIX: Sync is slow if you have bookmarks with lot of links that change very often.
* FIXME0: Certificates error are not handled in --sync
* FIXME1: consider root file is always index.gmi
* FIXME2: offline web browser use os.system because its the only one that understands the ">> file.txt"
* TODO: Update blackbox to reflect cache hits.
## AV-98-offline
This is a fork of the original [AV-98](https://tildegit.org/solderpunk/AV-98) by Solderpunk.
This fork is an experiment by Ploum (=> gemini://rawtext.club/~ploum ) to add offline capabilities to AV-98, including a persistent "tour" option.
@ -18,27 +60,10 @@ New ressources discovered in your bookmarks while --sync will be added to your t
At the moment, caching only work for gemini:// ressources. gopher:// is not implemented and http(s):// ressources are sent to an "offline browser" (by default, None, nothing is done). It could be useful to, for example, send the http:// links to a text file in order to visit them while online.
Known issues in the code:
* WONTFIX: Sync is slow if you have bookmarks with lot of links that change very often.
* WONTFIX: Certificates error are not handled in --sync
* FIXME1: consider root file is always index.gmi
* FIXME2: offline web browser use os.system because its the only one that understands the ">> file.txt"
* TODO: Update blackbox to reflect cache hits.
## Original description
AV-98 is an experimental client for the
[Gemini protocol](https://gemini.circumlunar.space). It is derived from the
[gopher client VF-1](https://github.com/solderpunk/VF-1) by the same author.
AV-98 is "experimental" in the sense that it may occasionally extend or deviate
from the official Gemini specification for the purposes of, well,
experimentation. Despite this, it is expected to be stable enough for regular
daily use at the same time.
## Dependencies
AV-98 has no "strict dependencies", i.e. it will run and work without anything
Offpunk has no "strict dependencies", i.e. it will run and work without anything
else beyond the Python standard library. However, it will "opportunistically
import" a few other libraries if they are available to offer an improved
experience.
@ -48,6 +73,7 @@ experience.
* The [cryptography library](https://pypi.org/project/cryptography/) will
provide a better and slightly more secure experience when using the default
TOFU certificate validation mode and is highly recommended.
* [Python magic](https://github.com/ahupp/python-magic/) is useful to determine the MIME type of cached object. If not present, the file extension will be used but some capsules provide wrong extension or no extension at all.
## Features
@ -61,18 +87,6 @@ experience.
* IPv6 support
* Supports any character encoding recognised by Python
## Lightning introduction
You use the `go` command to visit a URL, e.g. `go gemini.circumlunar.space`.
Links in Gemini documents are assigned numerical indices. Just type an index to
follow that link.
If a Gemini document is too long to fit on your screen, use the `less` command
to pipe it to the `less` pager.
Use the `help` command to learn about additional commands.
## RC files
You can use an RC file to automatically run any sequence of valid AV-98

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# AV-98 Gemini client
# Dervied from VF-1 (https://github.com/solderpunk/VF-1),
# Offpunk Offline Gemini client
# Derived from AV-98 by Solderpunk,
# (C) 2021 Ploum <offpunk@ploum.eu>
# (C) 2019, 2020 Solderpunk <solderpunk@sdf.org>
# With contributions from:
# - danceka <hannu.hartikainen@gmail.com>
@ -66,7 +67,7 @@ _MAX_REDIRECTS = 5
_MAX_CACHE_SIZE = 10
_MAX_CACHE_AGE_SECS = 180
# TODO :use XDG spec for cache
_CACHE_PATH = "~/.cache/av98/"
_CACHE_PATH = "~/.cache/offpunk/"
# Command abbreviations
_ABBREVS = {
@ -309,7 +310,7 @@ class GeminiClient(cmd.Cmd):
# Find config directory
## Look for something pre-existing
for confdir in ("~/.av98/", "~/.config/av98/"):
for confdir in ("~/.offpunk/", "~/.config/offpunk/"):
confdir = os.path.expanduser(confdir)
if os.path.exists(confdir):
self.config_dir = confdir
@ -317,15 +318,15 @@ class GeminiClient(cmd.Cmd):
## Otherwise, make one in .config if it exists
else:
if os.path.exists(os.path.expanduser("~/.config/")):
self.config_dir = os.path.expanduser("~/.config/av98/")
self.config_dir = os.path.expanduser("~/.config/offpunk/")
else:
self.config_dir = os.path.expanduser("~/.av98/")
self.config_dir = os.path.expanduser("~/.offpunk/")
print("Creating config directory {}".format(self.config_dir))
os.makedirs(self.config_dir)
self.no_cert_prompt = "\x1b[38;5;76m" + "AV-98" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
self.cert_prompt = "\x1b[38;5;202m" + "AV-98" + "\x1b[38;5;255m"
self.offline_prompt = "\x1b[38;5;76m" + "OFF-98" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
self.no_cert_prompt = "\x1b[38;5;76m" + "ON" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
self.cert_prompt = "\x1b[38;5;202m" + "ON" + "\x1b[38;5;255m"
self.offline_prompt = "\x1b[38;5;76m" + "OFF" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
self.prompt = self.no_cert_prompt
self.gi = None
self.history = []
@ -401,7 +402,7 @@ class GeminiClient(cmd.Cmd):
first_seen date, last_seen date, count integer)""")
def _go_to_gi(self, gi, update_hist=True, check_cache=True, handle=True):
"""This method might be considered "the heart of AV-98".
"""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 if
its a menu, storing the response in a temporary file, choosing
@ -427,7 +428,7 @@ class GeminiClient(cmd.Cmd):
return
elif gi.scheme == "gopher" and not self.options.get("gopher_proxy", None)\
and not self.sync_only:
print("""AV-98 does not speak Gopher natively.
print("""Offpunk does not speak Gopher natively.
However, you can use `set gopher_proxy hostname:port` to tell it about a
Gopher-to-Gemini proxy (such as a running Agena instance), in which case
you'll be able to transparently follow links to Gopherspace!""")
@ -1384,7 +1385,7 @@ you'll be able to transparently follow links to Gopherspace!""")
print("Are you sure you don't want to pass the filename to the handler?")
def do_abbrevs(self, *args):
"""Print all AV-98 command abbreviations."""
"""Print all Offpunk command abbreviations."""
header = "Command Abbreviations:"
self.stdout.write("\n{}\n".format(str(header)))
if self.ruler:
@ -1394,19 +1395,20 @@ you'll be able to transparently follow links to Gopherspace!""")
self.stdout.write("\n")
def do_offline(self, *args):
"""Use AV-98 offline by only accessing cached content"""
"""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("AV-98 is now offline and will only access cached content")
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("AV-98 is online and will access the network")
print("Offpunk is online and will access the network")
else:
print("Already online. Try offline.")
@ -1593,7 +1595,7 @@ Think of it like marks in vi: 'mark a'='ma' and 'go a'=''a'."""
def do_version(self, line):
"""Display version information."""
print("AV-98 " + _VERSION)
print("Offpunk " + _VERSION)
### Stuff that modifies the lookup table
def do_ls(self, line):
@ -1726,7 +1728,7 @@ Use 'ls -l' to see URLs."""
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 AV-98
# "source code" of menus, not the rendered view - this way Offpunk
# can navigate to it later.
shutil.copyfile(self.tmp_filename, filename)
print("Saved to %s" % filename)
@ -1818,7 +1820,7 @@ current gemini browsing session."""
### The end!
def do_quit(self, *args):
"""Exit AV-98."""
"""Exit Offpunk."""
# Close TOFU DB
self.db_conn.commit()
self.db_conn.close()
@ -1835,7 +1837,7 @@ current gemini browsing session."""
if os.path.exists(certfile):
os.remove(certfile)
print()
print("Thank you for flying AV-98!")
print("You can close your screen!")
sys.exit()
do_exit = do_quit
@ -1862,14 +1864,14 @@ def main():
# Handle --version
if args.version:
print("AV-98 " + _VERSION)
print("Offpunk " + _VERSION)
sys.exit()
# Instantiate client
gc = GeminiClient(restricted=args.restricted,synconly=args.sync)
# Process config file
rcfile = os.path.join(gc.config_dir, "av98rc")
rcfile = os.path.join(gc.config_dir, "offpunkrc")
if os.path.exists(rcfile):
print("Using config %s" % rcfile)
with open(rcfile, "r") as fp:
@ -1887,7 +1889,7 @@ def main():
# Say hi
if not args.sync:
print("Welcome to AV-98!")
print("Welcome to Offpunk!")
if args.restricted:
print("Restricted mode engaged!")
print("Enjoy your patrol through Geminispace...")

View File

@ -1,12 +1,12 @@
from setuptools import setup
setup(
name='AV-98',
version='1.0.4dev',
description="Command line Gemini client.",
author="Solderpunk",
author_email="solderpunk@sdf.org",
url='https://tildegit.org/solderpunk/AV-98/',
name='offpunk',
version='0.1',
description="Offline Command line Gemini client forked from AV-98.",
author="Ploum",
author_email="offpunk@ploum.eu",
url='https://tildegit.org/ploum/AV-98-offline/',
classifiers=[
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 3 :: Only',
@ -15,9 +15,9 @@ setup(
'Environment :: Console',
'Development Status :: 4 - Beta',
],
py_modules = ["av98"],
py_modules = ["offpunk"],
entry_points={
"console_scripts": ["av98=av98:main"]
"console_scripts": ["offpunk=offpunk:main"]
},
install_requires=[],
)