💾 Archived View for gmn.clttr.info › sources › astro.git › tree › astro.txt captured on 2024-02-05 at 09:58:29.
⬅️ Previous capture (2023-12-28)
-=-=-=-=-=-=-
#!/bin/sh version="0.25.1" usage() { echo "astro v$version: Browse the gemini web on the terminal." echo "" echo "Usage: astro [URL]|[OPTION]" echo "" echo "Options:" echo " -h, --help show this help" echo " -v, --version show version info" echo "" echo "Configuration:" echo "You can setup a config file at ~/.config/astro/astro.conf to configure *astro* the way you like." echo "" echo "Commands:" echo "These are the default keybindings to use while running astro:" echo "" echo " q quit" echo " g go to a link" echo " r reload current page" echo " b go back one page" echo " u jump one path segment up" echo " o open an address" echo " s save current page" echo " H go to homepage" echo " m add bookmark" echo " M go to a bookmark" echo " K remove bookmark for current url" echo "" echo "Examples:" echo " astro Start browsing the default webpage" echo " astro url Start browsing url" echo " astro --help Show help" echo "" echo "Report bugs to: bleemayer@gmail.com" echo "Home page: <https://www.github.com/blmayer/astro/>" echo "General help: <https://www.github.com/blmayer/astro/wiki>" } version() { echo "astro $version" echo "" echo "Copyright (C) 2021-2023 Brian Mayer." echo "License MIT: MIT License <https://opensource.org/licenses/MIT>" echo "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND," echo "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF" echo "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT." echo "" echo "Written by Brian Lee Mayer." } debug() { [ "$debug" ] && echo "DEBUG: $*" >&2 && sleep "$debug" } # Parse arguments debug "parsing args" while [ "$1" ] do case $1 in "-v") debug=1 ;; "-h" | "--help") usage exit 0 ;; "--version") version exit 0 ;; "-f"|"--file") shift file="$1" ;; "") break ;; *) url="$1" ;; esac shift done # Save terminal tput smcup # Configuration confighome=${XDG_CONFIG_HOME:-$HOME/.config} mkdir -p "$confighome/astro" configfile="$confighome/astro/astro.conf" bookmarkfile="$confighome/astro/bookmarks" certdir="$confighome/astro/certs" mkdir -p "$certdir" cachedir="${XDG_CACHE_HOME:-$HOME/.cache}/astro" # Default values # user config margin=8 homepage="geminiprotocol.net/" # keybindings openkey='o' openlocalkey='O' gokey='g' refreshkey='r' backkey='b' quitkey='q' markkey='b' gomarkkey='M' delmarkkey='K' goupkey='u' homekey='H' # styles sty_header1='\033[35;7;1m' sty_header2='\033[35;4;1m' sty_header3='\033[35;4m' sty_quote='\033[2;3m ' sty_linkb='\033[35m' sty_linkt=' => \033[36;3m ' sty_listb='\033[35;1m •' sty_listt='\033[0m ' if [ ! -s "$configfile" ] then # Default values cat <<- EOF > "$configfile" # where to store temp files cachedir="$cachedir" # user config margin=8 homepage="gemini.circumlunar.space/" # keybindings openkey='o' openlocalkey='O' gokey='g' refreshkey='r' backkey='b' quitkey='q' markkey='b' gomarkkey='M' delmarkkey='K' goupkey='u' homekey='H' # styles sty_header1='\033[35;7;1m' sty_header2='\033[35;4;1m' sty_header3='\033[35;4m' sty_quote='\033[2;3m ' sty_linkb='\033[35m' sty_linkt=' => \033[36;3m ' sty_listb='\033[35;1m •' sty_listt='\033[0m ' EOF fi # shellcheck source=/dev/null . "$configfile" mkdir -p "$cachedir" pagefile="$(mktemp -p "$cachedir" -t curpage.XXXXXX)" histfile="$(mktemp -p "$cachedir" -t history.XXXXXX)" linksfile="$(mktemp -p "$cachedir" -t links.XXXXXX)" tracefile="$(mktemp -p "$cachedir" -t trace.XXXXXX)" debug "read configs" # Restore terminal trap 'rm -f $histfile $linksfile $pagefile $tracefile > /dev/null 2>&1 && tput rmcup && printf "\033[?25h" && stty echo && exit' EXIT INT HUP stop() { [ "$trace" ] || return if [ -z "$stopwatch" ] then stopwatch=$(date +%s.%N) else dur=$(echo "$(date +%s.%N) - $stopwatch" | bc) printf "%s took %s seconds\n" "$1" "$dur" >> "$tracefile" unset stopwatch fi } getprevious() { sed -i '$d' "$histfile" prev="$(tail -n 1 "$histfile")" sed -i '$d' "$histfile" echo "$prev" } # Returns the complete url scheme with gemini defaults # Parameters: url parseurl() { # Credits: https://stackoverflow.com/a/6174447/7618649 debug "parsing: $url oldhost: $oldhost oldpath: $oldpath" proto="$(echo "$url" | grep :// | sed -e 's,^\(.*://\).*,\1,g')" if [ "$proto" ] then url="$(echo "$url" | sed -e "s@$proto@@g")" else if [ "$oldhost" ] then case "$url" in "/"*) url="$oldhost$url" ;; *) oldpath="/${oldpath#/*}"; url="$oldhost${oldpath%/*}/$url" ;; esac fi fi debug "url: $url" proto="$(echo "$proto" | sed -e 's,:\?//,,g')" user="$(echo "$url" | grep @ | cut -d@ -f1)" hostport="$(echo "$url" | sed -e "s/$user@//g" | cut -d/ -f1)" host="$(echo "$hostport" | sed -e 's,:.*,,g')" port="$(echo "$hostport" | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')" path="$(echo "${url#/*}" | sed "s@/\?$hostport@@")" debug "parsed: proto: ${proto:-gemini} host: $host port: ${port:-1965} path: ${path#/*}" echo "${proto:-gemini}" "$host" "${port:-1965}" "${path#/*}" "$rest" } typesetgmi() { # some setup first [ -f "$linksfile" ] && rm "$linksfile" cols=$(tput cols) linkcount="0" # shellcheck disable=SC2154 width=$((cols - (2*margin))) debug "text width: $width" stop while IFS='' read -r line || [ -n "$line" ]; do # shellcheck disable=SC2059 line="$(printf "$line\n" | tr -d '\r')" printf "$line\n" | grep -q "^\`\`\`" && pre=$((1 - pre)) && line="" # add margins and fold if [ "$pre" = 1 ] then printf '%*s%s\n' "$margin" "" "$line" continue fi # shellcheck disable=SC2154 case "$line" in "### "*) sty="$sty_header3" && line="${line#'### '}" ;; "## "*) sty="$sty_header2" && line="${line#'## '}" ;; "# "*) sty="$sty_header1" && line="${line#'# '}" ;; "> "*) sty="$sty_quote" && line="${line#> }" ;; "=>"*) link="$(echo "${line#'=>'}" | tr -s '\t' ' ')" echo "${link#' '}" >> "$linksfile" linkcount=$((linkcount+1)) line="$(echo "$link" | cut -d' ' -f2-)" [ -z "$line" ] && line="$link" sty="$sty_linkb${linkcount}$sty_linkt" ;; '* '*) sty="$sty_listb$sty_listt" && line="${line#* }";; *) sty='' ;; esac # shellcheck disable=SC2059 printf -- "$line\n" | fold -w "$width" -s | while IFS='' read -r txt do printf "%*s" "$margin" "" # shellcheck disable=SC2059 printf -- "$sty$txt\n\033[m" done done stop "typeset" } # some help: # \033[;H move to top # \033[NH move to bottom N lines # \033[?25l hide cursor # \033[?25h unhide cursor # \033[2K erase line pager() { clear # hide cursor printf '\033[?25l' # lines columns lines="$(tput lines)" head -n "$((lines-1))" "$1" l="$(wc -l < "$1")" if [ "$lines" -lt "$l" ]; then pos="$lines"; else pos="$l"; fi # stop echoing user input and read input unbufered stty -echo -icanon min 1 time 0 # read inputs while k="$(dd bs=1 count=1 status=none | od -c -An | tr -d ' ')" do case "$k" in # command sequences "033") b="$(dd bs=2 count=1 status=none)" case "$b" in # up arrow '[A') [ "$pos" -le "$lines" ] && continue line="$(sed "$((pos-lines))q;d" "$1")" pos="$((pos-1))" printf '\033[H\033[L%s\033[%sH\033[2K' "$line" "$lines" ;; # down arrow '[B') [ "$pos" -ge "$l" ] && continue printf '\033[%sH' "$lines" sed "${pos}q;d" "$1" pos="$((pos+1))" ;; # page up '[5') # discard one extra byte dd bs=1 count=1 status=none > /dev/null [ "$pos" -le "$lines" ] && continue scroll="$((pos-lines))" [ "$scroll" -gt "$lines" ] && scroll="$((lines-1))" # shellcheck disable=SC2086 for i in $(seq 1 "$scroll") do line="$(sed "$((pos-lines))q;d" "$1")" pos="$((pos-1))" printf '\033[H\033[L%s\033[%sH\033[2K' "$line" "$lines" done ;; # page down '[6') # discard one extra byte dd bs=1 count=1 status=none > /dev/null [ "$pos" -ge "$l" ] && continue scroll="$((lines-1))" end="$((pos+scroll))" [ "$end" -ge "$l" ] && scroll="$((l-pos))" # shellcheck disable=SC2086 for i in $(seq 1 "$scroll") do printf '\033[%sH' "$lines" sed "${pos}q;d" "$1" pos="$((pos+1))" done ;; esac ;; "$quitkey") exit 0 ;; "$openkey") printf '\033[%sH\033[?25h\033[2KType url: ' "$lines" stty echo icanon read -r url <&1 # add gemini:// to begining if not (for convenience) case "$url" in "gemini://"*) ;; *) url="gemini://$url" ;; esac return ;; "$openlocalkey") # Open local gmi file printf '\033[?25h\033[2KType file path: ' stty echo read -r file <&1 typesetgmi < "$file" > "$pagefile" pager "$pagefile" return ;; "$refreshkey") return ;; "$gokey") printf '\033[?25h\033[2KEnter link number: ' stty echo icanon read -r i <&1 debug "selected $i" url="$(sed "${i}q;d" "$linksfile" | cut -f1 | cut -d' ' -f1)" return ;; "$backkey") read -r proto host port path <<- EOF $(getprevious) EOF url="$proto://$host:$port/$path" return ;; "$homekey") url="$homepage"; return ;; "$markkey") printf '\033[?25h\033[KEnter description: (optional)' stty echo icanon read -r desc <&1 echo "$url $desc" >> "$bookmarkfile" return ;; "$gomarkkey") clear cat -n "$bookmarkfile" printf "\033[?25h\033[KEnter link number: " stty echo icanon read -r i <&1 url="$(sed "${i}q;d" "$bookmarkfile" | cut -d' ' -f1)" return ;; "$delmarkkey") grep -iv "^$url " "$bookmarkfile" > "$cachedir/bookmarks" mv "$cachedir/bookmarks" "$bookmarkfile" return ;; "$goupkey") newpath=$(echo "$url" | rev | cut -d'/' -f2- | rev) url="${url%/*}/$newpath" return ;; esac done } # borrowed from https://gist.github.com/cdown/1163649 urlencode() { stop old_lang=$LANG LANG=C old_lc_collate=$LC_COLLATE LC_COLLATE=C length="${#1}" i=1 while [ "$i" -le "$length" ] do c=$(printf '%s' "$1" | cut -c $i) case $c in [a-zA-Z0-9.~_-]) printf '%s' "$c" ;; *) printf '%%%02X' "'$c" ;; esac i=$((i+1)) done LC_COLLATE=$old_lc_collate LANG=$old_lang stop "urlencode" } # Fetches the gemini response from server # Parameters: proto, host, port and path # Spec draft is here: https://gemini.circumlunar.space/docs/specification.html fetch() { if [ ! "$1" = "gemini" ] then echo "Only gemini links are supported." echo "Type a key to continue." read -r i <&1 read -r proto host port path <<- EOF $(getprevious) EOF url="$proto://$host:$port/$path" url="${url:-$homepage}" debug "previous page: $url" return fi debug "requesting $1://$2:$3/$4$5" # set title printf '\033]2;%s\007' "astro (c): $2/$4" echo "$1 $2 $3 $4 $5" >> "$histfile" certfile="" if [ -f "$certdir/$2.crt" ] && [ -f "$certdir/$2.key" ] then certfile="-cert \"$certdir/$2.crt\" -key \"$certdir/$2.key\"" debug "using client cert for domain: $certfile" fi port=$( [ "$3" = "1965" ] || ":$3" ) url="$1://$2$port/$4$5" [ "$trace" ] && echo "url: $url" >> "$tracefile" stop echo "$url" | eval openssl s_client \ -connect "$2:$3" "$certfile" -crlf -quiet \ -ign_eof 2> /dev/null > "$pagefile" stop "openssl fetch" stop printf '\033]2;%s\007' "astro (r): $2/$4" # First line is status and meta information read -r status meta < "$pagefile" status="$(echo "$status" | tr -d '\r\n')" meta="$(echo "$meta" | tr -d '\r\n')" sed -i '1d' "$pagefile" stop "status extract" debug "response status - meta: $status - $meta" # Validate case "$status" in 10) echo "Input needed: $meta" >&2 echo "Please provide the input:" >&2 read -r input <&1 url="$1://$2:$3/$4?$(urlencode "$input")" return 0 ;; 11) echo "Sensitive input needed: $meta" >&2 read -r input <&1 url="$1://$2:$3/$4?$(urlencode "$input")" return 0 ;; 30|31) # Redirect debug "redirecting to: $meta" # shellcheck disable=SC2046 read -r proto host port path <<- EOF $(oldhost="$2" oldpath="$4" url="$meta" parseurl) EOF url="$proto://$host:$port/$path" return 0 ;; 40) echo "Temporary failure" >&2 echo "Type a key to continue. " read -r i <&1 return 3 ;; 41) return 4 ;; 42) return 5 ;; 43) return 6 ;; 44) return 7 ;; 50|51) echo "Page not found!" >&2 echo "Type a key to return to previous page." read -r i <&1 read -r proto host port path <<- EOF $(getprevious) EOF url="$proto://$host:$port/$path" debug "previous page: $url" return 0 ;; 52) return 10 ;; 53) return 11 ;; 59) echo "Bad request: $meta" >&2 echo "Type a key to continue." read -r i <&1 return 12 ;; 60) printf "client certificate required, to create a client cert use the following command:\n\n" printf "\topenssl req -x509 -newkey rsa:4096 -keyout %s/%s.key -out %s/%s.crt -days 36500 -nodes\n\n" "$certdir" "$2" "$certdir" "$2" printf "press 'return' to reload the page or 'b' to go back to the previous page:\n" read -r in <&1 if [ "$in" = "b" ] then read -r proto host port path <<- EOF $(getprevious) EOF url="$proto://$host:$port/$path" else url="$1://$2:$3/$4?$5" fi return 0 ;; 61) return 14 ;; 62) return 15 ;; esac # Success oldhost="$2" oldpath="$4" # Set charset charset="$(echo "$meta" | grep -i "charset=" | sed -e 's/.*charset=\([^;]\+\).*/\1/Ig')" case "$charset" in "iso-8859-1" | "ISO-8859-1") charset="iso8859" ;; "utf-8" | "UTF-8" | "") charset="utf8" ;; "us-ascii" | "US-ASCII") charset="ascii" ;; esac debug "charset: $charset" printf '\033]2;%s\007' "astro (t): $2/$4" case $meta in "text/gemini"* | "") typesetgmi < "$pagefile" > "$pagefile.gmi"; mv "$pagefile.gmi" "$pagefile" ;; *) ;; esac debug "starting pager" printf '\033]2;%s\007' "astro (l): $2/$4" pager "$pagefile" debug "new url: $url" } # files are handled differently if [ "$file" ] && [ -f "$file" ] then typesetgmi < "$file" > "$pagefile" pager "$pagefile" fi # first request url="${url:-$homepage}" while : do printf '\033]2;%s\007' "astro (w)" # shellcheck disable=SC2046 fetch $(parseurl) done