#!/usr/bin/env bash # nf (netfinger): A netcat-based finger client. # Features: user@domain and finger:// URL support, HTML & Markdown rendering, # smart wrapping, blocklists, custom shortcuts, CR/LF detection, error handling # and protocol validation. # Reqs: nc, awk, less, fold (standard on most Linux systems) # pandoc & lynx (for Markdown support; may require installation) # 4.4 : 2026-01-19 : removed timeout for now: nc -w 3 "$host" -> nc "$host" 79 # 4.3 : 2026-01-17 : edit: help section # 4.2 : 2026-01-15 : added "--mdg", browser variable (GUI_BROWSER) # 4.1 : 2026-01-15 : added "-s" flag to pandoc; other tweaks # 4.0 : 2026-01-10 : added Markdown support (-m/--md). Req: pandoc & lynx # 3.6 : 2025-12-04 : Filename changed to "nf", various improvements # 3.5 : 2025-12-01 : HTML renderer: better URL detection, punctuation handling # 3.0 : 2025-11-28 : added --wrap and --render flags # 2.0 : 2025-11-27 : multi-location weather support; per server blocklist # 1.0 : 2025-11-24 : initial release # Copyright (c) 2025-2026 (https://640kb.neocities.org) # Licensed under the Blue Oak Model License 1.0.0 if [ $# -eq 1 ] && [[ "$1" =~ ^(-h|--help|/\?|-\?)$ ]]; then echo echo " .-------------------------------------------------------------." echo " | nf (netfinger): A finger client using netcat. |" echo " '-------------------------------------------------------------'" echo " .-------------------------------------------------------------." echo " | Usage: nf user@domain.com |" echo " | nf finger://domain.com/user |" echo " '-------------------------------------------------------------'" echo " .-------------------------------------------------------------." echo " | Examples: nf user@domain.com |" echo " | nf @domain.com | head -n 20 |" echo " | nf finger://domain.com/user |" echo " | |" echo " | nf -w finger://domain.com/user |" echo " | nf -r user@domain.com |" echo " | nf -m user@domain.com |" echo " '-------------------------------------------------------------'" echo " .-------------------------------------------------------------." echo " | Options: -l --list Displays shortcuts (see code) |" echo " | -w --wrap Smart wrap using fold & less |" echo " | -r --render Output
HTML; clickable links |"
echo " | -m --md Render Markdown. Pandoc & Lynx |"
echo " | --mdg Render Markdown (GUI Browser) |"
echo " |-------------------------------------------------------------|"
echo " | blocklist edit --hnb and --plan in shortcuts section |"
echo " | wrap width edit the function wrap_text() |"
echo " | GUI_BROWSER edit for --mdg mode (default=firefox) |"
echo " '-------------------------------------------------------------'"
echo
exit 0
fi
# ---------------------------------------------------------------------------
# SHORTCUTS section: Examples below. Adjust for personal use (no arrays, no
# config files - just easy to edit case statements) or you can delete this
# entire section without affecting the script's core function.
case "$1" in
--ansi)
"$0" ansi@happynetbox.com | head -n -2 | echo -e "$(cat)"
exit 0
;;
--verse)
"$0" fingerverse@happynetbox.com | less
exit 0
;;
--text)
"$0" textfile@typed-hole.org | less
exit 0
;;
--weather)
{
echo "================================================================================"
echo
"$0" ^'New York, New York'@graph.no
echo
echo "================================================================================"
echo
"$0" ^'Death Valley, California'@graph.no
echo
echo "================================================================================"
echo
"$0" ^'Moscow, Russia'@graph.no
echo
echo "================================================================================"
echo
"$0" ^'Beijing, China'@graph.no
echo
echo "================================================================================"
echo
"$0" ^"roswell, new mexico"@graph.no
echo
echo "================================================================================"
echo
"$0" ^"Easter Island, Chile"@graph.no
echo
echo "================================================================================"
echo
"$0" ^'Beaver Creek, Yukon'@graph.no
echo
echo "================================================================================"
echo
} | less
exit 0
;;
--say)
"$0" say@happynetbox.com | head -n 25
exit 0
;;
# Blocklist: Edit the "grep -v..." lines to add/remove usernames from your blocklist
--hnb)
"$0" @happynetbox.com \
| tail -n +5 | head -n -7 \
| grep -v -i -w trollacct1 \
| grep -v -i -w spammer1 \
| grep -v -i -w trollacct2
exit 0
;;
# Blocklist: Edit the "grep -v..." lines to add/remove usernames from your blocklist
--plan)
"$0" @plan.cat \
| head -n 20 \
| grep -v -i -w trollacct1 \
| grep -v -i -w spammer1 \
| grep -v -i -w trollacct2
exit 0
;;
-l|--list)
printf "%s\n" --ansi --verse --text --weather --say --hnb --plan --list
exit 0
;;
esac
# Shortcuts section: END
# ---------------------------------------------------------------------------
WRAP_MODE=false
RENDER_MODE=false
MD_MODE=false
MDG_MODE=false
FINGER_TARGET=""
# GUI Browsers for --mdg mode:
# Works (tested): "vivaldi-stable --disable-vivaldi" "vivaldi-stable" "firefox", ""dillo" ("xlinks2" aka links2 in graphics mode)
# Does not work: "brave-browser-stable"
# Untested: I have many browsers installed as Snap and Flatpak
GUI_BROWSER="firefox"
while [[ $# -gt 0 ]]; do
case "$1" in
-w|--wrap)
WRAP_MODE=true
shift
;;
-r|--render)
RENDER_MODE=true
shift
;;
-m|--md)
MD_MODE=true
shift
;;
--mdg)
MDG_MODE=true
shift
;;
-*)
echo "Error: Unknown flag $1" >&2
exit 1
;;
*)
if [ -z "$FINGER_TARGET" ]; then
FINGER_TARGET="$1"
shift
else
echo "Error: Multiple finger targets specified" >&2
exit 1
fi
;;
esac
done
if [ -z "$FINGER_TARGET" ]; then
echo
echo " Error: Missing finger target" >&2
echo " Use 'nf -h' for help" >&2
echo
exit 1
fi
input="$FINGER_TARGET"
if [[ "$input" == finger://* ]]; then
path="${input#finger://}"
if [[ "$path" == */* ]]; then
host="${path%%/*}"
user="${path#*/}"
else
host="$path"
user=""
fi
if [ -n "$user" ]; then
input="$user@$host"
else
input="@$host"
fi
fi
if [[ "$input" == *://* ]]; then
protocol="${input%%://*}"
echo "Error: Only finger protocol is supported (got $protocol:// instead)" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# Edit to change wrapping width when using the "-w" option. Default is 80.
wrap_text() {
fold -s -w "${1:-80}" | less
}
# ---------------------------------------------------------------------------
render_html() {
awk '{
while(match($0, /(https?|gemini|finger|spartan|gopher):\/\/[^[:space:]<>"'"'"']*/)) {
url = substr($0, RSTART, RLENGTH)
after = substr($0, RSTART + RLENGTH)
# ver 3.5 update: Strip trailing punctuation from all URLs EXCEPT Wikipedia.
if (url ~ /[.,;:!?)]$/ && !(url ~ /wikipedia\.org/)) {
after = substr(url, length(url)) after
url = substr(url, 1, length(url) - 1)
}
printf "%s%s", substr($0, 1, RSTART - 1), url, url
$0 = after
}
print
}'
}
if [[ "$input" == @* ]]; then
host="${input#@}"
output=$(printf '\r\n' | nc "$host" 79 2>/dev/null || echo | nc "$host" 79 2>/dev/null)
elif [[ "$input" == *@* ]]; then
user="${input%@*}"
host="${input#*@}"
output=$(printf '%s\r\n' "$user" | nc "$host" 79 2>/dev/null || echo "$user" | nc "$host" 79 2>/dev/null)
else
echo "Error: Input must be in format user@host or finger://host/user" >&2
exit 1
fi
if $MDG_MODE; then
TMP=$(mktemp --suffix=.html) && echo "$output" | pandoc -s -f markdown -t html -o "$TMP" && $GUI_BROWSER "$TMP" && rm "$TMP"
elif $MD_MODE; then
echo "$output" | pandoc -s -f markdown -t html | lynx -stdin
# I may revert to syntax below in the future...
# echo "$output" | pandoc -s -f markdown+pipe_tables+backtick_code_blocks+autolink_bare_uris+strikeout+hard_line_breaks -t html | lynx -stdin
#
# other options:
# LSS file: lynx -lss=~/path/to/file.lss -stdin (see: https://640kb.neocities.org/fingerverse/lynx/lynx.tips.txt)
# nf markdown@happynetbox.com | pandoc -s -f markdown -t html | w3m -T text/html
# nf markdown@happynetbox.com | pandoc -s -f markdown -t html -o output.html
elif $RENDER_MODE; then
echo ""
echo "$output" | render_html
echo ""
elif $WRAP_MODE; then
echo "$output" | wrap_text 80
else
echo "$output"
echo # optional: added a blank line for improved readability of finger query
fi