qutebrowser: Refactor gemini userscript

Now possibly a little less clunky (though still using soon-to-be
deprecated modules), and able to correctly surf gemini/non-gemini pages
as a userscript or as a stand-alone script (simply calling it from
command line).

Works as before for tabbed pages (the clunky rename/symlink way).

Can now also be used as the default way to surf to links so I switched
out my qutebrowser f/F link hinting for this script. Will simply open
links if staying on http but open gemini version as local file if moving
on to gemini.

Could probably be rewritten as an actual plugin to interject itself in
link opening to be a little more elegant (similar to the redirect code I
have running to move to open source web frontends).
This commit is contained in:
Marty Oehme 2023-08-08 15:52:12 +02:00
parent f09a75820e
commit f7304b8941
Signed by: Marty
GPG key ID: EDBF2ED917B2EF6A
3 changed files with 78 additions and 33 deletions

View file

@ -1,4 +1,4 @@
c.aliases["gem"] = "hint links userscript qute-gemini"
c.aliases["gem"] = "spawn --userscript qute-gemini "
# Use q for quitting a tab (mimicks vim buffer) - qa is used for exiting
c.aliases["q"] = "tab-close"

View file

@ -6,6 +6,10 @@ lleader = ","
## CHANGED DEFAULTS
# the full power of qutebrowser at your fingertips for any gemini page!
config.bind("f", "hint links userscript qute-gemini")
config.bind("F", "hint links userscript qute-gemini-tab")
# rebind moving tabs to free for download
config.bind("gG", "tab-give")
# switch binds for scroll-marks and quick-/book-marks

View file

@ -6,6 +6,20 @@
# SPDX-FileCopyrightText: 2020 petedussin
# SPDX-FileCopyrightText: 2020-2021 Sotiris Papatheodorou
# SPDX-License-Identifier: GPL-3.0-or-later
# 2022-2023 Marty Oehme (added stand-alone script capability)
# Use it as a qutebrowser userscript to open gemini pages:
# Put this file in qutebrowser userscript folder and call command
# `:spawn --userscript qute-gemini "gemini://my-gemini-url.org"`
# or
# `:hint links userscript qute-gemini` to open from selected link
# Rename file to `qute-gemini-tab` (or create symlink) to open
# any gemini url as a new tab.
# Since the script also opens normal URLs you can even replace your
# normal link hint mapping with it (usually f or F for tabbed) and
# continue surfing like normal, only that you can now also access
# any gemini pages as if they were part of the normal http protocol.
import cgi
import html
@ -41,7 +55,8 @@ CSS
_status_code_desc = {
"1": "Gemini status code 1 Input. This is not implemented in qute-gemini.",
"10": "Gemini status code 10 Input. This is not implemented in qute-gemini.",
"11": "Gemini status code 11 Sensitive Input. This is not implemented in qute-gemini.",
"11": """Gemini status code 11 Sensitive Input. This is not implemented
in qute-gemini.""",
"3": "Gemini status code 3 Redirect. Stopped after "
+ str(_max_redirects)
+ " redirects.",
@ -53,28 +68,41 @@ _status_code_desc = {
+ " redirects.",
"4": "Gemini status code 4 Temporary Failure. Server message: META",
"40": "Gemini status code 40 Temporary Failure. Server message: META",
"41": "Gemini status code 41 Server Unavailable. The server is unavailable due to overload or maintenance. Server message: META",
"42": "Gemini status code 42 CGI Error. A CGI process, or similar system for generating dynamic content, died unexpectedly or timed out. Server message: META",
"43": "Gemini status code 43 Proxy Error. A proxy request failed because the server was unable to successfully complete a transaction with the remote host. Server message: META",
"44": "Gemini status code 44 Slow Down. Rate limiting is in effect. Please wait META seconds before making another request to this server.",
"41": """Gemini status code 41 Server Unavailable.
The server is unavailable due to overload or maintenance. Server message: META""",
"42": """Gemini status code 42 CGI Error.
A CGI process, or similar system for generating dynamic content,
died unexpectedly or timed out. Server message: META""",
"43": """Gemini status code 43 Proxy Error.
A proxy request failed because the server was unable to successfully
complete a transaction with the remote host. Server message: META""",
"44": """Gemini status code 44 Slow Down. Rate limiting is in effect.
Please wait META seconds before making another request to this server.""",
"5": "Gemini status code 5 Permanent Failure. Server message: META",
"50": "Gemini status code 50 Permanent Failure. Server message: META",
"51": "Gemini status code 51 Not Found. he requested resource could not be found but may be available in the future. Server message: META",
"52": "Gemini status code 52 Gone. The resource requested is no longer available and will not be available again. Server message: META",
"53": "Gemini status code 53 Proxy Request Refused. The request was for a resource at a domain not served by the server and the server does not accept proxy requests. Server message: META",
"59": "Gemini status code 59 Bad Request. The server was unable to parse the client's request, presumably due to a malformed request. Server message: META",
"6": "Gemini status code 6 Client Certificate Required. This is not implemented in qute-gemini.",
"51": """Gemini status code 51 Not Found. The requested resource could
not be found but may be available in the future. Server message: META""",
"52": """Gemini status code 52 Gone. The resource requested is no longer
available and will not be available again. Server message: META""",
"53": """Gemini status code 53 Proxy Request Refused. The request was for
a resource at a domain not served by the server and the server does
not accept proxy requests. Server message: META""",
"59": """Gemini status code 59 Bad Request. The server was unable to
parse the client's request, presumably due to a malformed request.
Server message: META""",
"6": """Gemini status code 6 Client Certificate Required.
This is not implemented in qute-gemini.""",
}
def qute_url() -> str:
"""Get the URL passed to the script by qutebrowser."""
return os.environ["QUTE_URL"]
return os.environ.get("QUTE_URL", "")
def qute_fifo() -> str:
"""Get the FIFO or file to write qutebrowser commands to."""
return os.environ["QUTE_FIFO"]
return os.environ.get("QUTE_FIFO", "")
def html_href(url: str, description: str) -> str:
@ -118,7 +146,7 @@ def gemini_fetch_url(url: str) -> Tuple[str, str, str, str, str]:
url = "gemini://" + url
parsed_url = urllib.parse.urlparse(url)
if parsed_url.scheme != "gemini":
return "", "Received non-gemini:// URL: " + url
return "", "Received non-gemini:// URL: " + url, "59", "", "Non-gemini URL"
if parsed_url.port is not None:
useport = parsed_url.port
else:
@ -214,15 +242,15 @@ def gemtext_to_html(
pass
# Link
elif line.startswith("=>"):
l = line[2:].split(None, 1)
ln = line[2:].split(None, 1)
# Use the URL itself as the description if there is none
if len(l) == 1:
l.append(l[0])
if len(ln) == 1:
ln.append(ln[0])
# Encode the link description
l[1] = html.escape(l[1])
ln[1] = html.escape(ln[1])
# Resolve relative URLs
l[0] = gemini_absolutise_url(url, l[0])
lines.append("\t\t<p>" + html_href(l[0], l[1]) + "</p>")
ln[0] = gemini_absolutise_url(url, ln[0])
lines.append("\t\t<p>" + html_href(ln[0], ln[1]) + "</p>")
# Preformated toggle
elif line.startswith("```"):
if in_pre:
@ -316,7 +344,7 @@ def qute_error_page(url: str, description: str) -> str:
return "data:text/html;charset=UTF-8," + urllib.parse.quote(html_page)
def open_gemini(url: str, open_args: str) -> None:
def open_gemini(url: str) -> str:
"""Open Gemini URL in qutebrowser."""
# Get the Gemini content
content, content_url, status, meta, error_msg = gemini_fetch_url(url)
@ -328,18 +356,30 @@ def open_gemini(url: str, open_args: str) -> None:
tmpf = tempfile.NamedTemporaryFile("w", suffix=".html", delete=False)
tmp_filename = tmpf.name
tmpf.close()
if not tmp_filename:
return ""
with open(tmp_filename, "w") as f:
f.write(gemtext_to_html(content, content_url, url, status, meta))
open_url = " file://" + tmp_filename
# Open the HTML file in qutebrowser
with open(qute_fifo(), "w") as qfifo:
qfifo.write("open " + open_args + open_url)
return open_url
def open_other(url: str, open_args: str) -> None:
"""Open non-Gemini URL in qutebrowser."""
with open(qute_fifo(), "w") as qfifo:
qfifo.write("open " + open_args + " " + url)
def open_url(url: str, open_args: str) -> None:
parsed_url = urllib.parse.urlparse(url)
if parsed_url.scheme == "gemini":
to_open = open_gemini(url)
else:
to_open = url
if not to_open:
return
fifo = qute_fifo()
if fifo and fifo != "":
with open(fifo, "w") as qfifo:
qfifo.write(f"open {open_args} {to_open}")
return
os.system(f"xdg-open {to_open}")
if __name__ == "__main__":
@ -348,10 +388,11 @@ if __name__ == "__main__":
open_args = "-t"
else:
open_args = ""
# Select how to open the URL depending on its scheme
url = qute_url()
parsed_url = urllib.parse.urlparse(url)
if parsed_url.scheme == "gemini":
open_gemini(url, open_args)
# Take url to open as argument or from qutebrowser url
if len(sys.argv) > 1:
url = sys.argv[1]
else:
open_other(url, open_args)
url = qute_url()
# Select how to open the URL depending on its scheme
open_url(url, open_args)