Introducing Offpunk
This commit is contained in:
parent
3d66f99c95
commit
700cba7184
80
README.md
80
README.md
|
@ -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 it’s 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.
|
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.
|
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 it’s 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
|
## 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
|
else beyond the Python standard library. However, it will "opportunistically
|
||||||
import" a few other libraries if they are available to offer an improved
|
import" a few other libraries if they are available to offer an improved
|
||||||
experience.
|
experience.
|
||||||
|
@ -48,6 +73,7 @@ experience.
|
||||||
* The [cryptography library](https://pypi.org/project/cryptography/) will
|
* The [cryptography library](https://pypi.org/project/cryptography/) will
|
||||||
provide a better and slightly more secure experience when using the default
|
provide a better and slightly more secure experience when using the default
|
||||||
TOFU certificate validation mode and is highly recommended.
|
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
|
## Features
|
||||||
|
|
||||||
|
@ -61,18 +87,6 @@ experience.
|
||||||
* IPv6 support
|
* IPv6 support
|
||||||
* Supports any character encoding recognised by Python
|
* 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
|
## RC files
|
||||||
|
|
||||||
You can use an RC file to automatically run any sequence of valid AV-98
|
You can use an RC file to automatically run any sequence of valid AV-98
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# AV-98 Gemini client
|
# Offpunk Offline Gemini client
|
||||||
# Dervied from VF-1 (https://github.com/solderpunk/VF-1),
|
# Derived from AV-98 by Solderpunk,
|
||||||
|
# (C) 2021 Ploum <offpunk@ploum.eu>
|
||||||
# (C) 2019, 2020 Solderpunk <solderpunk@sdf.org>
|
# (C) 2019, 2020 Solderpunk <solderpunk@sdf.org>
|
||||||
# With contributions from:
|
# With contributions from:
|
||||||
# - danceka <hannu.hartikainen@gmail.com>
|
# - danceka <hannu.hartikainen@gmail.com>
|
||||||
|
@ -66,7 +67,7 @@ _MAX_REDIRECTS = 5
|
||||||
_MAX_CACHE_SIZE = 10
|
_MAX_CACHE_SIZE = 10
|
||||||
_MAX_CACHE_AGE_SECS = 180
|
_MAX_CACHE_AGE_SECS = 180
|
||||||
# TODO : use XDG spec for cache
|
# TODO : use XDG spec for cache
|
||||||
_CACHE_PATH = "~/.cache/av98/"
|
_CACHE_PATH = "~/.cache/offpunk/"
|
||||||
|
|
||||||
# Command abbreviations
|
# Command abbreviations
|
||||||
_ABBREVS = {
|
_ABBREVS = {
|
||||||
|
@ -309,7 +310,7 @@ class GeminiClient(cmd.Cmd):
|
||||||
|
|
||||||
# Find config directory
|
# Find config directory
|
||||||
## Look for something pre-existing
|
## Look for something pre-existing
|
||||||
for confdir in ("~/.av98/", "~/.config/av98/"):
|
for confdir in ("~/.offpunk/", "~/.config/offpunk/"):
|
||||||
confdir = os.path.expanduser(confdir)
|
confdir = os.path.expanduser(confdir)
|
||||||
if os.path.exists(confdir):
|
if os.path.exists(confdir):
|
||||||
self.config_dir = confdir
|
self.config_dir = confdir
|
||||||
|
@ -317,15 +318,15 @@ class GeminiClient(cmd.Cmd):
|
||||||
## Otherwise, make one in .config if it exists
|
## Otherwise, make one in .config if it exists
|
||||||
else:
|
else:
|
||||||
if os.path.exists(os.path.expanduser("~/.config/")):
|
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:
|
else:
|
||||||
self.config_dir = os.path.expanduser("~/.av98/")
|
self.config_dir = os.path.expanduser("~/.offpunk/")
|
||||||
print("Creating config directory {}".format(self.config_dir))
|
print("Creating config directory {}".format(self.config_dir))
|
||||||
os.makedirs(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.no_cert_prompt = "\x1b[38;5;76m" + "ON" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
|
||||||
self.cert_prompt = "\x1b[38;5;202m" + "AV-98" + "\x1b[38;5;255m"
|
self.cert_prompt = "\x1b[38;5;202m" + "ON" + "\x1b[38;5;255m"
|
||||||
self.offline_prompt = "\x1b[38;5;76m" + "OFF-98" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
|
self.offline_prompt = "\x1b[38;5;76m" + "OFF" + "\x1b[38;5;255m" + "> " + "\x1b[0m"
|
||||||
self.prompt = self.no_cert_prompt
|
self.prompt = self.no_cert_prompt
|
||||||
self.gi = None
|
self.gi = None
|
||||||
self.history = []
|
self.history = []
|
||||||
|
@ -401,7 +402,7 @@ class GeminiClient(cmd.Cmd):
|
||||||
first_seen date, last_seen date, count integer)""")
|
first_seen date, last_seen date, count integer)""")
|
||||||
|
|
||||||
def _go_to_gi(self, gi, update_hist=True, check_cache=True, handle=True):
|
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:
|
Everything involved in fetching a gemini resource happens here:
|
||||||
sending the request over the network, parsing the response if
|
sending the request over the network, parsing the response if
|
||||||
its a menu, storing the response in a temporary file, choosing
|
its a menu, storing the response in a temporary file, choosing
|
||||||
|
@ -427,7 +428,7 @@ class GeminiClient(cmd.Cmd):
|
||||||
return
|
return
|
||||||
elif gi.scheme == "gopher" and not self.options.get("gopher_proxy", None)\
|
elif gi.scheme == "gopher" and not self.options.get("gopher_proxy", None)\
|
||||||
and not self.sync_only:
|
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
|
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
|
Gopher-to-Gemini proxy (such as a running Agena instance), in which case
|
||||||
you'll be able to transparently follow links to Gopherspace!""")
|
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?")
|
print("Are you sure you don't want to pass the filename to the handler?")
|
||||||
|
|
||||||
def do_abbrevs(self, *args):
|
def do_abbrevs(self, *args):
|
||||||
"""Print all AV-98 command abbreviations."""
|
"""Print all Offpunk command abbreviations."""
|
||||||
header = "Command Abbreviations:"
|
header = "Command Abbreviations:"
|
||||||
self.stdout.write("\n{}\n".format(str(header)))
|
self.stdout.write("\n{}\n".format(str(header)))
|
||||||
if self.ruler:
|
if self.ruler:
|
||||||
|
@ -1394,19 +1395,20 @@ you'll be able to transparently follow links to Gopherspace!""")
|
||||||
self.stdout.write("\n")
|
self.stdout.write("\n")
|
||||||
|
|
||||||
def do_offline(self, *args):
|
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:
|
if self.offline_only:
|
||||||
print("Offline and undisturbed.")
|
print("Offline and undisturbed.")
|
||||||
else:
|
else:
|
||||||
self.offline_only = True
|
self.offline_only = True
|
||||||
self.prompt = self.offline_prompt
|
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):
|
def do_online(self, *args):
|
||||||
|
"""Use Offpunk online with a direct connection"""
|
||||||
if self.offline_only:
|
if self.offline_only:
|
||||||
self.offline_only = False
|
self.offline_only = False
|
||||||
self.prompt = self.no_cert_prompt
|
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:
|
else:
|
||||||
print("Already online. Try offline.")
|
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):
|
def do_version(self, line):
|
||||||
"""Display version information."""
|
"""Display version information."""
|
||||||
print("AV-98 " + _VERSION)
|
print("Offpunk " + _VERSION)
|
||||||
|
|
||||||
### Stuff that modifies the lookup table
|
### Stuff that modifies the lookup table
|
||||||
def do_ls(self, line):
|
def do_ls(self, line):
|
||||||
|
@ -1726,7 +1728,7 @@ Use 'ls -l' to see URLs."""
|
||||||
print("File %s already exists!" % filename)
|
print("File %s already exists!" % filename)
|
||||||
else:
|
else:
|
||||||
# Don't use _get_active_tmpfile() here, because we want to save the
|
# 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.
|
# can navigate to it later.
|
||||||
shutil.copyfile(self.tmp_filename, filename)
|
shutil.copyfile(self.tmp_filename, filename)
|
||||||
print("Saved to %s" % filename)
|
print("Saved to %s" % filename)
|
||||||
|
@ -1818,7 +1820,7 @@ current gemini browsing session."""
|
||||||
|
|
||||||
### The end!
|
### The end!
|
||||||
def do_quit(self, *args):
|
def do_quit(self, *args):
|
||||||
"""Exit AV-98."""
|
"""Exit Offpunk."""
|
||||||
# Close TOFU DB
|
# Close TOFU DB
|
||||||
self.db_conn.commit()
|
self.db_conn.commit()
|
||||||
self.db_conn.close()
|
self.db_conn.close()
|
||||||
|
@ -1835,7 +1837,7 @@ current gemini browsing session."""
|
||||||
if os.path.exists(certfile):
|
if os.path.exists(certfile):
|
||||||
os.remove(certfile)
|
os.remove(certfile)
|
||||||
print()
|
print()
|
||||||
print("Thank you for flying AV-98!")
|
print("You can close your screen!")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
do_exit = do_quit
|
do_exit = do_quit
|
||||||
|
@ -1862,14 +1864,14 @@ def main():
|
||||||
|
|
||||||
# Handle --version
|
# Handle --version
|
||||||
if args.version:
|
if args.version:
|
||||||
print("AV-98 " + _VERSION)
|
print("Offpunk " + _VERSION)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# Instantiate client
|
# Instantiate client
|
||||||
gc = GeminiClient(restricted=args.restricted,synconly=args.sync)
|
gc = GeminiClient(restricted=args.restricted,synconly=args.sync)
|
||||||
|
|
||||||
# Process config file
|
# Process config file
|
||||||
rcfile = os.path.join(gc.config_dir, "av98rc")
|
rcfile = os.path.join(gc.config_dir, "offpunkrc")
|
||||||
if os.path.exists(rcfile):
|
if os.path.exists(rcfile):
|
||||||
print("Using config %s" % rcfile)
|
print("Using config %s" % rcfile)
|
||||||
with open(rcfile, "r") as fp:
|
with open(rcfile, "r") as fp:
|
||||||
|
@ -1887,7 +1889,7 @@ def main():
|
||||||
|
|
||||||
# Say hi
|
# Say hi
|
||||||
if not args.sync:
|
if not args.sync:
|
||||||
print("Welcome to AV-98!")
|
print("Welcome to Offpunk!")
|
||||||
if args.restricted:
|
if args.restricted:
|
||||||
print("Restricted mode engaged!")
|
print("Restricted mode engaged!")
|
||||||
print("Enjoy your patrol through Geminispace...")
|
print("Enjoy your patrol through Geminispace...")
|
16
setup.py
16
setup.py
|
@ -1,12 +1,12 @@
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='AV-98',
|
name='offpunk',
|
||||||
version='1.0.4dev',
|
version='0.1',
|
||||||
description="Command line Gemini client.",
|
description="Offline Command line Gemini client forked from AV-98.",
|
||||||
author="Solderpunk",
|
author="Ploum",
|
||||||
author_email="solderpunk@sdf.org",
|
author_email="offpunk@ploum.eu",
|
||||||
url='https://tildegit.org/solderpunk/AV-98/',
|
url='https://tildegit.org/ploum/AV-98-offline/',
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Programming Language :: Python :: 3 :: Only',
|
'Programming Language :: Python :: 3 :: Only',
|
||||||
|
@ -15,9 +15,9 @@ setup(
|
||||||
'Environment :: Console',
|
'Environment :: Console',
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
],
|
],
|
||||||
py_modules = ["av98"],
|
py_modules = ["offpunk"],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": ["av98=av98:main"]
|
"console_scripts": ["offpunk=offpunk:main"]
|
||||||
},
|
},
|
||||||
install_requires=[],
|
install_requires=[],
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue