#!/bin/bash # Convert email to HTML and open in browser with images emlfile="/tmp/neomutt-email-$$.eml" tmpdir="/tmp/neomutt-email-$$" tmpfile="$tmpdir/email-$$.html" # Save email from stdin mkdir -p "$tmpdir" cat >"$emlfile" # Extract MIME parts using ripmime if available if command -v ripmime &>/dev/null; then ripmime -i "$emlfile" -d "$tmpdir" --no-nameless 2>/dev/null elif command -v munpack &>/dev/null; then cd "$tmpdir" && munpack -q "$emlfile" 2>/dev/null fi # Check if there's an HTML part extracted html_part=$(find "$tmpdir" -maxdepth 1 -name "*.html" -o -name "*.htm" 2>/dev/null | head -1) if [[ -n $html_part && -s $html_part ]]; then # Use extracted HTML directly cp "$html_part" "$tmpfile" else # Generate HTML from text { echo "" echo '' echo "" echo "" echo '' echo "" # Extract and display headers (handles multi-line headers) echo '
' awk 'BEGIN{IGNORECASE=1; show=0; header=""; value=""} function decode_qp(str, result, i, hex, char) { result = str # Decode =XX hex sequences while (match(result, /=[0-9A-Fa-f][0-9A-Fa-f]/)) { hex = substr(result, RSTART+1, 2) cmd = "printf \"\\x" hex "\"" cmd | getline char close(cmd) result = substr(result, 1, RSTART-1) char substr(result, RSTART+3) } gsub(/_/, " ", result) return result } function decode_mime(str, result, before, after, encoded, charset, encoding, text) { result = str # Decode =?charset?Q?text?= or =?charset?B?text?= while (match(result, /=\?[^?]+\?[QqBb]\?[^?]+\?=/)) { before = substr(result, 1, RSTART-1) encoded = substr(result, RSTART, RLENGTH) after = substr(result, RSTART+RLENGTH) # Remove =?...?Q? and ?= gsub(/=\?[^?]+\?[Qq]\?/, "", encoded) gsub(/\?=$/, "", encoded) text = decode_qp(encoded) result = before text after } return result } function split_addresses(str, arr, n, i, c, inquote, current) { n = 0; inquote = 0; current = "" for (i = 1; i <= length(str); i++) { c = substr(str, i, 1) if (c == "\"") inquote = !inquote if (c == "," && !inquote) { gsub(/^[ \t]+|[ \t]+$/, "", current) if (current != "") arr[++n] = current current = "" } else { current = current c } } gsub(/^[ \t]+|[ \t]+$/, "", current) if (current != "") arr[++n] = current return n } function output() { if (header != "" && value != "") { value = decode_mime(value) gsub(//, "\\>", value) # Only split To, CC, Bcc addresses (not Date, Subject, From) if (tolower(header) ~ /^(from|to|cc|bcc)$/) { n = split_addresses(value, addrs) printf "

" header ": " if (n > 1) { for (i=1; i<=n; i++) printf "" addrs[i] "" } else { printf "" value "" } print "

" } else if (tolower(header) == "subject") { print "

" header ": " value "

" } else { print "

" header ": " value "

" } } header = ""; value = "" } /^$/{output(); exit} /^(From|To|CC|Bcc|Reply-To|Subject|Date):/{ output() show=1 match($0, /^[^:]+/) header = substr($0, 1, RLENGTH) value = substr($0, RLENGTH+2) gsub(/^[ \t]+|[ \t]+$/, "", value) next } /^[A-Za-z0-9-]+:/ && !/^(From|To|CC|Bcc|Reply-To|Subject|Date):/{output(); show=0} show && /^[ \t]/{ gsub(/^[ \t]+/, "", $0) value = value " " $0 } END{output()}' "$emlfile" echo "
" # Display body - extract text part and format code blocks echo '
' # Try to find extracted text file first text_part=$(find "$tmpdir" -maxdepth 1 -name "textfile*" -o -name "*.txt" 2>/dev/null | head -1) if [[ -n $text_part && -s $text_part ]]; then body_content=$(cat "$text_part") elif command -v mshow &>/dev/null; then body_content=$(mshow -N "$emlfile" 2>&1 | grep -v "no filter or default handler" | grep -viE "^(From|To|Cc|Bcc|Reply-To|Subject|Date):") else body_content=$(sed '1,/^$/d' "$emlfile") fi # Process body: escape HTML, handle code blocks, inline code, and quoted text # First pass: detect quote blocks (lines starting with > followed by more > lines) echo "$body_content" | awk ' BEGIN { incode=0; inquote=0; quotebuf=""; quotecount=0 } function flush_quote() { if (quotebuf != "") { # Only make collapsible if we have 3+ quoted lines n = split(quotebuf, lines, "\n") if (n >= 3) { print "
▶ Show quoted text (" n " lines)
"
                        print quotebuf
                        print "
" } else { print "" quotebuf "" } quotebuf = "" } } /^```/ { flush_quote() if (incode) { print ""; incode=0 } else { print "
"; incode=1 }
                next
            }
            /^>/ {
                if (incode) { gsub(//, "\\>"); print; next }
                gsub(//, "\\>")
                if (quotebuf != "") quotebuf = quotebuf "\n"
                quotebuf = quotebuf $0
                next
            }
            function linkify(line,    result, pos, url, email, before, after, linktext) {
                result = line
                # First: Handle email pattern - keep only one email as link
                while (match(result, /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}<mailto:[^&]*>/)) {
                    before = substr(result, 1, RSTART-1)
                    # Extract just the email part (before <mailto:)
                    pos = index(substr(result, RSTART), "<mailto:")
                    email = substr(result, RSTART, pos-1)
                    after = substr(result, RSTART+RLENGTH)
                    result = before "" email "" after
                }
                # Second: Handle standalone <mailto:email>
                while (match(result, /<mailto:[^&]+>/)) {
                    before = substr(result, 1, RSTART-1)
                    email = substr(result, RSTART+11, RLENGTH-15)
                    after = substr(result, RSTART+RLENGTH)
                    result = before "" email "" after
                }
                # Third: Handle text<https://url> - text becomes link text
                while (match(result, /[A-Za-z0-9_-]+<https?:\/\/[^&]+>/)) {
                    before = substr(result, 1, RSTART-1)
                    full = substr(result, RSTART, RLENGTH)
                    # Find where < starts
                    pos = index(full, "<")
                    linktext = substr(full, 1, pos-1)
                    # Extract URL: after < (4 chars) until > (4 chars at end)
                    url = substr(full, pos+4, length(full)-pos-7)
                    after = substr(result, RSTART+RLENGTH)
                    result = before "" linktext "" after
                }
                # Fourth: Handle standalone <https://url>
                while (match(result, /<https?:\/\/[^&]+>/)) {
                    before = substr(result, 1, RSTART-1)
                    url = substr(result, RSTART+4, RLENGTH-8)
                    after = substr(result, RSTART+RLENGTH)
                    result = before "" url "" after
                }
                # Fifth: Handle plain URLs (not already in href)
                while (match(result, /https?:\/\/[^ &<>"\n\t]+/)) {
                    before = substr(result, 1, RSTART-1)
                    if (before ~ /href="$/) break
                    url = substr(result, RSTART, RLENGTH)
                    after = substr(result, RSTART+RLENGTH)
                    # Clean trailing punctuation
                    sub(/[.,;:!?)]+$/, "", url)
                    result = before "" url "" after
                }
                return result
            }
            {
                flush_quote()
                # HTML escape first
                gsub(//, "\\>")
                if (!incode) {
                    # Handle inline code `code`
                    while (match($0, /`[^`]+`/)) {
                        before = substr($0, 1, RSTART-1)
                        code = substr($0, RSTART+1, RLENGTH-2)
                        after = substr($0, RSTART+RLENGTH)
                        $0 = before "" code "" after
                    }
                    # Linkify URLs and emails
                    $0 = linkify($0)
                }
                print
            }
            END {
                flush_quote()
                if (incode) print "
" } ' echo "
" # Show extracted images find "$tmpdir" -maxdepth 1 -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.gif" \) 2>/dev/null | while read -r img; do echo "

" done echo "" } >"$tmpfile" fi open "$tmpfile" rm -f "$emlfile" # Clean up after 60 seconds in background (sleep 60 && rm -rf "$tmpdir") &