#!/bin/bash

# Title......: mdlinks
# Description: link checker for markdown file
# Author.....: Mitchell Johnston - uid 0
# Contact....: mitch@crn.hopto.org
# Updated....: Sun Oct 11 08:25:26 AM CDT 2026
#----------------------------------

#This will default to index.md in current directory unless you pass it the name of a file to work on.

# Sections: Changes variables functions setup main 
# use '#' in vi/vim to jump to word under cursor


: ' Changes:
Sun Oct 11 2026 Created
'

# variables
#----------------------------------
[ "$1" == "-D" ] && DEBUG=1 && shift 1     # -D to turn on debug mode
[ "$DEBUG" == 1 ] && echo "---Variables---" && set -x
#FILE="${1:?Usage: $0 <file.md>}"
FILE="${1:-index.md}"

[[ $2 -lt 600 ]] && export GREP_COLORS='ms=01;31'
[[ $2 -lt 400 ]] && export GREP_COLORS='ms=01;33'
[[ $2 -lt 300 ]] && export GREP_COLORS='ms=01;32'
[[ $1 = "-c" ]] && egrep --color=always "^${2}" $0  && exit

PS4='$SECONDS $LINENO: '                   # debug prompt
DOW=$(date +%a)                            # day of week: Thu
TODAY=$(date +%m/%d)                       # month/day: 03/25
DOM=$(date +%d)                            # day of month: 25
OS=$(uname -s)                             # OS type: SunOS Linux
NAME=${0##*/}                              # name of the script
DIR="$(basename $PWD)"                     # location started
PDIR="$(cd ..;basename $PWD)"              # parrent dir


# functions
#----------------------------------
html(){ ## mark up code
    vim -f +"syn on" +"set nonu" +"set foldenable!" +"set nospell" +"run! syntax/2html.vim" +"wq" +"q" $1
}

log(){ ## creates a basic log entry $LOG must be defined
    # Use: log {entry}  
    [ "$DEBUG" == "1" ] && set -x
    logger -i -t "$NAME" "$*"
}

pause(){ ## simple pause routine
    # Use: pause  {optional number of seconds} or "-nt" for no time out 
    [ "$DEBUG" == "1" ] && set -x
    [ "$1" == "-nt" ] && TMOUT="" && shift
    echo "$BY";
    if [ $# -gt 0 ]
    then
        read -t $1 -r -p "${C}Hit any key (${BY}$1${C} second timeout)${N}" -n 1 FOO;
    else
        read -r -p "${C}Hit any key${N}" -n 1 FOO;
    fi;
    bl
}

strip-tags(){ ## remove HTML tags
  # Use: curl https://linuxhint.com/bash_cut_command/ --silent | strip-tags
  sed -e 's/<[^>]*.//g' -
}

xtitle(){ ## set window title
    # Use: xtitle "Text to display"
    printf "\033]0;%s\007" "$*"
}

version(){ ## display version and change history
    grep -E '^# Updated' $0
    bl
    sed -n "/' Changes/,/^ *$/p" <$0 |grep  -E -v 'sed -n|exit 0|}'
    exit 0
}

# setup
#----------------------------------

# part of my code management system
# if you want a function or peice of code from another script:
#  !!{script} -F {function}
#  will insert the code.
[[ "$1" = "-V" ]] && bat -p -l bash $0 && exit
if [[ "$1" = "-F" ]]
    then
        [[ "$#" -eq 2 ]] && declare -f "$2" |bat -p -l bash && exit
        [[ "$#" -eq 1 ]] && declare -f |bat -p -l bash && exit
fi

# this provides a quick way to edit all my scripts on the fly
if [ "$1" == "-E" ]
then
    vim $0
    sed -i -e "7s/.*/# Updated....: $(date)/" $0
    log "Updated $0"
    html $0
    cp $0 /var/www/unix
    mv $0.html /var/www/unix
    exit
fi

# display help if needed
if [ "$1" == "-h" ] || [ "$1" == "--help" ]
then
fmt -s -w $(tput cols) <<END
$NAME {file}

Defaults to index.md in current directory. Works with any markdown file.

-c {code} will look up an HTML return code meaning.

END
exit
fi

# display version and change history
if [ "$1" == "-v" ] || [ "$1" == "--version" ]
then
    version
fi

# listing of required apps
command -v bash >/dev/null || sudo apt install bash -qyy

# main
#--------------------------- 
[ "$DEBUG" == 1 ] && echo "---Functions---" && typeset -F && read -p "${N}-Continue-" -n 1 x
[ "$DEBUG" == 1 ] && echo "---Main---" && set -x

# Temp file to collect failures
FAILURES=$(mktemp)
trap 'rm -f "$FAILURES"' EXIT

# Extract URLs
grep -oP '(?<=\]\()https?://[^)\s]+(?=\))|(?<=<)https?://[^>\s]+(?=>)' "$FILE" \
  | grep -v '^#' \
  | sort -u \
  | while read -r url; do
#    result=$(curl -o /dev/null -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" -s -L --head --write-out '%{http_code}' --max-time 10 "$url" 2>/dev/null)
result=$(curl -o /dev/null \
  -s -L \
  --connect-timeout 5 \
  --max-time 10 \
  -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" \
  -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
  -H "Accept-Language: en-US,en;q=0.9" \
  -H "Sec-Fetch-Dest: document" \
  -H "Sec-Fetch-Mode: navigate" \
  -H "Sec-Fetch-Site: none" \
  --write-out '%{http_code}' \
  "$url" 2>/dev/null)

    case "$result" in
      2*) echo "✅ ${result}  $url" ;;
      3*) echo "🔄 ${result}  $url" ;;
      4*) echo "❌ ${result}  $url"; echo "$result $url" >> "$FAILURES" ;;
      5*) echo "🚨 ${result}  $url"; echo "$result $url" >> "$FAILURES" ;;
      *)  echo "⏱️  TIMEOUT  $url"; echo "TIMEOUT $url" >> "$FAILURES" ;;
    esac
  done

# Summary
echo ""
echo "────────────────────────────────────"
if [[ -s "$FAILURES" ]]; then
  count=$(wc -l < "$FAILURES")
  echo "📋 ${count} broken link(s):"
  echo ""
  cat "$FAILURES" | while read -r code url; do
    echo "   • ${code}  ${url}"
  done
else
  echo "🎉 All links OK!"
fi

exit
## Codes

# 1xx Informational
100 Continue: Client should continue with the request.
101 Switching Protocols: Server is switching protocols as requested.
102 Processing: Server has received the request and is processing it (WebDAV).
103 Early Hints: Server is sending partial response headers before the final response.

# 2xx Success
200 OK: Request succeeded; the content is returned in the response body.
201 Created: Request succeeded and a new resource was created.
202 Accepted: Request accepted for processing, but processing is not complete.
204 No Content: Server successfully processed the request and is not returning any content.
206 Partial Content: Server is delivering only part of the resource due to a Range header.

# 3xx Redirection
300 Multiple Choices: The request has more than one possible response.
301 Moved Permanently: The resource has been permanently moved to a new URI.
302 Found: The resource temporarily resides under a different URI.
303 See Other: The response to the request can be found under a different URI using a GET method.
304 Not Modified: The resource has not been modified since the last request.
307 Temporary Redirect: The resource temporarily resides under a different URI; the method must not change.
308 Permanent Redirect: The resource has been permanently moved to a new URI; the method must not change.

# 4xx Client Error
400 Bad Request: The server cannot process the request due to client error (e.g., malformed syntax).
401 Unauthorized: Authentication is required and has failed or not been provided.
402 Payment Required: Reserved for future use.
403 Forbidden: The server understands the request but refuses to authorize it.
404 Not Found: The server cannot find the requested resource.
405 Method Not Allowed: The request method is known by the server but is not supported by the resource.
406 Not Acceptable: The requested resource is capable of generating only content not acceptable according to the Accept headers.
407 Proxy Authentication Required: The client must first authenticate itself with the proxy.
408 Request Timeout: The server timed out waiting for the request.
409 Conflict: The request could not be completed due to a conflict with the current state of the resource.
410 Gone: The requested resource is no longer available and will not be available again.
411 Length Required: The request did not specify the length of its content.
412 Precondition Failed: The server does not meet one of the preconditions in the request.
413 Payload Too Large: The request entity is larger than limits defined by server.
414 URI Too Long: The URI requested is too long for the server to process.
415 Unsupported Media Type: The content type is not supported by the server.
416 Range Not Satisfiable: The client has asked for a portion of the file, but the server cannot supply that portion.
417 Expectation Failed: The server cannot meet the requirements of the Expect request-header field.
421 Misdirected Request: The request was directed at a server that is not able to produce a response.
422 Unprocessable Entity: The server understands the content type but cannot process the contained instructions.
423 Locked: The resource that is being accessed is locked.
424 Failed Dependency: The request failed due to failure of a previous request.
425 Too Early: The server is unwilling to risk processing a request that might be replayed.
426 Upgrade Required: The client should switch to a different protocol.
428 Precondition Required: The origin server requires the request to be conditional.
429 Too Many Requests: The user has sent too many requests in a given amount of time.
431 Request Header Fields Too Large: The server is unwilling to process the request because its header fields are too large.
451 Unavailable For Legal Reasons: The resource is unavailable due to legal reasons.

# 5xx Server Error
500 Internal Server Error: A generic error message indicating an unexpected condition was encountered.
501 Not Implemented: The server does not support the functionality required to fulfill the request.
502 Bad Gateway: The server was acting as a gateway or proxy and received an invalid response from the upstream server.
503 Service Unavailable: The server is currently unable to handle the request due to temporary overload or maintenance.
504 Gateway Timeout: The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
505 HTTP Version Not Supported: The server does not support the HTTP protocol version used in the request.
506 Variant Also Negotiates: The server has an internal configuration error.
507 Insufficient Storage: The server is unable to store the representation needed to complete the request.
508 Loop Detected: The server detected an infinite loop while processing the request.
510 Not Extended: Further extensions to the request are required for the server to fulfill it.
511 Network Authentication Required: The client needs to authenticate to gain network access.