#!/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