#!/usr/bin/env bash
set -o pipefail
# ============================================================
# Hastebin / pste.us uploader
#
# Dependencies:
# curl jq gum fzf qrencode
#
# Clipboard:
# wl-copy / xclip / xsel / pbcopy
# ============================================================
UPLOAD_URL="https://pste.us/documents"
BASE_URL="https://pste.us"
# Catppuccin-ish colors
BLUE="#89b4fa"
GREEN="#a6e3a1"
RED="#f38ba8"
YELLOW="#f9e2af"
CYAN="#89dceb"
MAUVE="#cba6f7"
TEXT="#cdd6f4"
DIM="#6c7086"
TMP_RESPONSE=""
TMP_PROGRESS=""
# ------------------------------------------------------------
# Cleanup
# ------------------------------------------------------------
cleanup() {
[[ -n "$TMP_RESPONSE" && -f "$TMP_RESPONSE" ]] && rm -f "$TMP_RESPONSE"
[[ -n "$TMP_PROGRESS" && -f "$TMP_PROGRESS" ]] && rm -f "$TMP_PROGRESS"
}
trap cleanup EXIT
trap 'exit 130' INT TERM
# ------------------------------------------------------------
# Helpers
# ------------------------------------------------------------
error() {
gum style \
--foreground "$RED" \
--border double \
--border-foreground "$RED" \
--padding "0 2" \
"✗ $1"
exit 1
}
# ------------------------------------------------------------
# Dependencies
# ------------------------------------------------------------
for cmd in curl jq gum fzf; do
command -v "$cmd" &>/dev/null || \
error "Missing dependency: $cmd"
done
# ------------------------------------------------------------
# Arguments
# ------------------------------------------------------------
FILE_PATH="${1:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
cat <<'EOF'
Usage:
hastebin [FILE]
Examples:
hastebin
hastebin file.txt
EOF
exit 0
;;
-*)
error "Unknown option: $1"
;;
*)
FILE_PATH="$1"
shift
;;
esac
done
# ------------------------------------------------------------
# File picker
# ------------------------------------------------------------
if [[ -z "$FILE_PATH" ]]; then
FILE_PATH=$(
find . -maxdepth 1 -type f -printf '%f\n' |
fzf \
--prompt=" Select file to upload: " \
--border \
--reverse \
--header="Use ↑/↓ to select • Enter to confirm" \
--pointer="➜" \
--color="bg+:#1e1e2e,bg:#181825,spinner:#f5c2e7,hl:#f38ba8,fg:#cdd6f4,header:#89b4fa,info:#f9e2af,pointer:#a6e3a1,marker:#f5c2e7,fg+:#cdd6f4,prompt:#89b4fa,hl+:#f38a8a" \
--preview='bat --style=numbers --color=always {}'
)
[[ -n "$FILE_PATH" ]] || exit 0
fi
# ------------------------------------------------------------
# Validate
# ------------------------------------------------------------
[[ -f "$FILE_PATH" ]] || error "File does not exist: $FILE_PATH"
[[ -r "$FILE_PATH" ]] || error "File is not readable: $FILE_PATH"
# ------------------------------------------------------------
# File information
# ------------------------------------------------------------
FILE_NAME="$(basename "$FILE_PATH")"
FILE_BYTES=$(stat -c%s "$FILE_PATH" 2>/dev/null || stat -f%z "$FILE_PATH")
if command -v numfmt &>/dev/null; then
FILE_SIZE=$(numfmt --to=iec --suffix=B "$FILE_BYTES")
else
FILE_SIZE=$(du -h "$FILE_PATH" | cut -f1)
fi
FILE_TYPE=$(file -b "$FILE_PATH" 2>/dev/null | cut -d',' -f1)
# Keep type short enough for the terminal
if (( ${#FILE_TYPE} > 34 )); then
FILE_TYPE="${FILE_TYPE:0:31}..."
fi
# ------------------------------------------------------------
# Temporary files
# ------------------------------------------------------------
TMP_RESPONSE=$(mktemp)
TMP_PROGRESS=$(mktemp)
# ------------------------------------------------------------
# Header
# ------------------------------------------------------------
clear
gum style \
--foreground "$BLUE" \
--border double \
--border-foreground "$BLUE" \
--align center \
--width 58 \
--padding "0 1" \
" HASTEBIN"
echo
gum style \
--foreground "$TEXT" \
--border rounded \
--border-foreground "$DIM" \
--padding "0 1" \
" $FILE_NAME • $FILE_SIZE • $FILE_TYPE"
echo
# ------------------------------------------------------------
# Upload
# ------------------------------------------------------------
printf ' '
gum style \
--foreground "$CYAN" \
--padding "0 0" \
" Uploading..."
echo
# Start upload
curl \
-fS \
--progress-bar \
-X POST \
-H "Content-Type: text/plain" \
--data-binary @"$FILE_PATH" \
"$UPLOAD_URL" \
>"$TMP_RESPONSE" \
2>"$TMP_PROGRESS" &
CURL_PID=$!
LAST_PERCENT=-1
# ------------------------------------------------------------
# In-place progress bar
# ------------------------------------------------------------
while kill -0 "$CURL_PID" 2>/dev/null; do
CURRENT_PERCENT=$(
tr '\r' '\n' < "$TMP_PROGRESS" 2>/dev/null |
grep -oE '[0-9]{1,3}(\.[0-9]+)?%' |
tail -1 |
tr -d '%' |
cut -d. -f1
)
[[ -n "$CURRENT_PERCENT" ]] || CURRENT_PERCENT=0
(( CURRENT_PERCENT > 100 )) && CURRENT_PERCENT=100
(( CURRENT_PERCENT < 0 )) && CURRENT_PERCENT=0
BAR_WIDTH=38
FILLED=$(( CURRENT_PERCENT * BAR_WIDTH / 100 ))
EMPTY=$(( BAR_WIDTH - FILLED ))
FILLED_BAR=$(printf '%*s' "$FILLED" '' | tr ' ' '━')
EMPTY_BAR=$(printf '%*s' "$EMPTY" '' | tr ' ' '─')
# Move to beginning of line and erase it.
printf '\r\033[2K'
printf ' \033[38;2;166;227;161m%s\033[0m \033[38;2;108;112;134m%s\033[0m \033[38;2;205;214;244m%3d%%\033[0m' \
"$FILLED_BAR" \
"$EMPTY_BAR" \
"$CURRENT_PERCENT"
LAST_PERCENT="$CURRENT_PERCENT"
sleep 0.08
done
wait "$CURL_PID"
CURL_STATUS=$?
# Finish progress line
printf '\r\033[2K'
if [[ "$CURL_STATUS" -ne 0 ]]; then
error "Upload failed."
fi
# ------------------------------------------------------------
# Parse response
# ------------------------------------------------------------
HASTE_KEY=$(jq -r '.key // empty' "$TMP_RESPONSE" 2>/dev/null)
[[ -n "$HASTE_KEY" ]] || error "Invalid response from server."
URL="$BASE_URL/$HASTE_KEY"
# ------------------------------------------------------------
# Copy URL
# ------------------------------------------------------------
COPIED=false
if command -v wl-copy &>/dev/null; then
printf '%s' "$URL" | wl-copy
COPIED=true
elif command -v xclip &>/dev/null; then
printf '%s' "$URL" | xclip -selection clipboard
COPIED=true
elif command -v xsel &>/dev/null; then
printf '%s' "$URL" | xsel --clipboard --input
COPIED=true
elif command -v pbcopy &>/dev/null; then
printf '%s' "$URL" | pbcopy
COPIED=true
fi
# ------------------------------------------------------------
# QR code
# ------------------------------------------------------------
QR=""
if command -v qrencode &>/dev/null; then
QR=$(
qrencode \
-t ANSIUTF8 \
-m 0 \
"$URL"
)
fi
# ------------------------------------------------------------
# Final panel
# ------------------------------------------------------------
echo
if [[ -n "$QR" ]]; then
gum style \
--border double \
--border-foreground "$GREEN" \
--padding "0 1" \
--align center \
--width 58 \
"✓ UPLOAD COMPLETE" \
"" \
" $FILE_NAME • $FILE_SIZE" \
"" \
"$URL" \
"" \
"$QR"
else
gum style \
--border double \
--border-foreground "$GREEN" \
--padding "0 2" \
--align center \
--width 58 \
"✓ UPLOAD COMPLETE" \
"" \
" $FILE_NAME • $FILE_SIZE" \
"" \
"$URL"
fi
# ------------------------------------------------------------
# Clipboard status
# ------------------------------------------------------------
echo
if [[ "$COPIED" == true ]]; then
gum style \
--foreground "$GREEN" \
--align center \
--width 58 \
" URL copied to clipboard"
else
gum style \
--foreground "$YELLOW" \
--align center \
--width 58 \
"⚠ Clipboard utility not found"
fi
echo