imgbb-uploader/imgbb

327 lines
9.9 KiB
Bash
Executable File

#!/bin/bash
# -----------------------------------------------------------------------------
# Script Name: imgbb
# Description: Uploads an image from the system clipboard or a specified file
# to ImgBB (imgbb.com) using their V1 API. Retrieves the direct
# image URL, prints it to stdout, and copies it to the X11 clipboard.
# Author: Ritchie Cunningham <ritchie@ritchiecunningham.co.uk>
# Contributors: dacav <dacav@fastmail.com>
# License: GPL 3.0 License
# Version: 1.2
# Date: 22/04/2025
# -----------------------------------------------------------------------------
# === Default Configuration & Argument Parsing. ===
api_key=""
config_file="$HOME/.config/imgbb_uploader.conf"
filepath=""
expire_seconds="7200" # 2h - Set to "" for no expiry. or use flag -e 0, never or none.
custom_name=""
markdown_mode="false"
clipboard="$XDG_SESSION_TYPE"
[ ! -e "$config_file" ] || . "$config_file"
print_usage() {
echo "Usage: $(basename "$0") [options] [filepath]"
echo "Uploads image from clipboard (default) or filepath to ImgBB."
echo ""
echo "Options:"
echo " [filepath] Optional path to an image file to upload."
echo " -e, --expire DURATION Set auto-deletion timeout. DURATION is a number"
echo " followed by s(econds), m(inutes), h(ours), or d(ays)."
echo " Default unit is minutes if unspecified."
echo " '0', 'none', 'never' to override default expiry."
echo " -n, --name NAME Set a custom filename for the uploaded image."
echo " --markdown Output/copy the URL in Markdown image format."
echo " -h, --help Show this help message."
}
die() {
declare msg
msg="$*"
notify_cmd -u critical "ImgBB Upload Error" "$msg" &>/dev/null
echo "$msg" >&2
exit 1
}
# Parse command-line options.
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-e|--expire)
expire_input="$2" # Usr input.
# Check for 'no expiry value'.
if [[ "$expire_input" == "0" || "$expire_input" == "never" || "$expire_input" == 'none' ]]; then
expire_seconds="" # Unset for no expiration.
echo "Setting NO expiration."
else
# Proceed with parsing regular.
value="" unit="" seconds=""
# Regex to capture the number part and optional unit part (s,m,h,d)
if [[ "$expire_input" =~ ^([0-9]+)([smhd]?)$ ]]; then
value="${BASH_REMATCH[1]}" # The numeric part.
unit="${BASH_REMATCH[2]}" # The unit part.
# Default to minutes if no unit was provided.
if [ -z "$unit" ]; then unit="m"; fi
# Calculate total seconds based on the unit.
case "$unit" in
s) seconds=$((value)) ;;
m) seconds=$((value * 60)) ;;
h) seconds=$((value * 3600)) ;; # 60*60.
d) seconds=$((value * 86400)) ;; # 60*60*24.
*) # Not really requried given the regex, but hey-ho.
echo "Error: Internal error parsing unit '$unit' from '$expire_input'."
exit 1 ;;
esac
# Validate against ImgBB limits (60 seconds to 15552000 seconds / 180 days).
if (( seconds < 60 )) || (( seconds > 15552000 )); then
echo "Error: Calculated expiration ($seconds seconds) is outside of ImgBB's allowed range (60s - 15552000s)." >&2
exit 1
fi
# Store the final result.
expire_seconds="$seconds"
echo "Expiration set to $expire_seconds seconds ($expire_input)"
else
# Someone did funky input. Tell them to stop that!
echo "Error: Invalid expiration format '$expire_input'." >&2
echo "Use a number optionally followed by s, m, h or d OR use 0/none/never (e.g., 300, 5m, 2h, 7d)." >&2
print_usage # Maybe they need help.
exit 1
fi
fi
shift
shift
;;
-n|--name)
custom_name="$2"
shift
shift
;;
--markdown)
markdown_mode="true"
shift
;;
-h|--help)
print_usage
exit 0
;;
-*)
# Unknown option.
echo "Error: Unknown option '$1'" >&2
print_usage
exit 1
;;
*)
# Assume it's the filepath (only one allowed).
if [ -n "$filepath" ]; then
echo "Error: Multiple filepaths provided ($filepath, $1). Only one allowed." >&2
print_usage
exit 1
fi
filepath="$1"
shift
;;
esac
done
# Check if filepath exists and is readable if provided.
if [ -n "$filepath" ] && [ ! -r "$filepath" ]; then
echo "Error: Cannot read file '$filepath'" >&2
exit 1
fi
# === Read API Key. ===
if [[ -n "$IMGBB_API_KEY" ]]; then
api_key="$IMGBB_API_KEY"
fi
# Check if API key is actually set.
if [ -z "$api_key" ]; then
# Use function defined below if notify-send exists.
notify_cmd -u critical "ImgBB Upload Error" "API Key not found." &>/dev/null
echo "Error: API Key not found." >&2
echo "Please store your key in '$config_file' or set the IMGBB_API_KEY environment variable." >&2
exit 1
fi
# === Check Dependencies. ===
check_command() {
if ! command -v "$1" &> /dev/null; then
notify_cmd -u critical "ImgBB Upload Error" "'$1' command not found. Please install it." &>/dev/null
echo "Error: '$1' command not found. Please install it (e.g., sudo apt install $1)." >&2
exit 1
fi
}
# Define notify_cmd first based on whether notify-send exists.
if ! command -v notify-send &> /dev/null; then
notify_cmd() { :; } # No-op if not found.
echo "Warning: notify-send not found. Desktop notifications disabled." >&2
else
notify_cmd() { notify-send "$@"; } # Actual command if found.
fi
# Now check other dependencies.
check_command curl
check_command jq
# Only check the clipboard command if we plan to use the clipboard.
if [ -z "$filepath" ]; then
case "$clipboard" in
x11)
check_command xclip
;;
wayland)
check_command wl-paste
;;
*)
die "Unsupported clipboard: $clipboard"
esac
fi
# === Main Logic. ===
paste_x11() {
declare tmp
tmp_img="$(mktemp)" || die "Could not create temp file."
xclip -selection clipboard -t image/png -o >"$tmp_img" 2>/dev/null
if [ -s "$tmp_img" ]; then
mv "$tmp_img" "$tmp_img.png"
printf "%s\n" "$tmp_img.png"
return 0
fi
xclip -selection clipboard -t image/jpeg -o >"$tmp_img" 2>/dev/null
if [ -s "$tmp_img" ]; then
mv "$tmp_img" "$tmp_img.jpeg"
printf "%s\n" "$tmp_img.jpeg"
return 0
fi
rm -f "$tmp_img"
die "Could not get PNG or JPEG image data from clipboard."
}
paste_wl() {
declare tmp_img
declare format
format="$(wl-paste -l | grep -m1 -F -e image/png -e image/jpeg)" ||
die "Invalid file type in clipboard."
tmp_img="$(mktemp)" || die "Could not create temp file."
if wl-paste -t "$format" >"$tmp_img" 2>/dev/null; then
mv "$tmp_img" "$tmp_img.${format##image/}"
printf "%s\n" "$tmp_img.${format##image/}"
return 0
fi
rm -f "$tmp_img"
die "Could not get PNG or JPEG image data from clipboard."
}
yank_x11() {
xclip -selection clipboard
}
yank_wl() {
wl-copy
}
TMP_IMG="" # Path to the image file to upload.
image_source_description=""
# Setup trap *only* if using clipboard mode.
if [ -z "$filepath" ]; then
# === Clipboard Input Mode. ===
image_source_description="clipboard"
TMP_IMG="$(
case "$clipboard" in
x11) paste_x11;;
wayland) paste_wl;;
esac
)" || exit
trap 'rm -f "$TMP_IMG"' EXIT
else
# === File Input Mode. ===
image_source_description="file '$filepath'"
TMP_IMG="$filepath" # Use the provided file path directly.
echo "Using image from file: $filepath"
fi
# Build curl options.
curl_opts=(-s -S -f -L -X POST)
curl_opts+=(--form "key=$api_key")
curl_opts+=(--form "image=@$TMP_IMG")
if [ -n "$expire_seconds" ]; then
curl_opts+=(--form "expiration=$expire_seconds")
fi
if [ -n "$custom_name" ]; then
curl_opts+=(--form "name=$custom_name")
echo "Setting custom name to '$custom_name'."
fi
# Upload the image using curl.
notify_cmd "ImgBB Uploader" "Uploading image from $image_source_description..."
response=$(curl "${curl_opts[@]}" 'https://api.imgbb.com/1/upload')
# Check curl exit status.
if [ $? -ne 0 ]; then
notify_cmd -u critical "ImgBB Upload Error" "curl command failed during upload. Check network?"
echo "Error: curl command failed during upload. Check network connection." >&2
exit 1
fi
# Parse the JSON response using jq.
# Check for overall success status from ImgBB API.
success=$(echo "$response" | jq -r '.success')
if [ "$success" != "true" ]; then
error_msg=$(echo "$response" | jq -r '.error.message // "Unknown API error"')
notify_cmd -u critical "ImgBB Upload Error" "API Error: $error_msg"
echo "Error: ImgBB API returned an error - $error_msg" >&2
echo "Full response: $response" >&2
exit 1
fi
# Extract the direct image URL.
image_url=$(echo "$response" | jq -r '.data.url')
# Check if URL was successfully extracted.
if [ -z "$image_url" ] || [ "$image_url" == "null" ]; then
notify_cmd -u critical "ImgBB Upload Error" "Could not parse image URL from API response."
echo "Error: Could not parse image URL from API response." >&2
echo "Full response: $response" >&2
exit 1
fi
# Format output based on markdown flag.
output_url="$image_url"
if [ "$markdown_mode" = "true" ]; then
# Basic markdown image syntax - assumes filename is not needed for alt text here.
output_url="![](${image_url})"
echo "Formatting as Markdown."
fi
# Output the URL to terminal and copy to clipboard.
echo "Image URL:"
echo "$output_url"
# Copy URL only if we determined we are not uploading from file (i.e., we used clipboard input)
# Or always copy? Let's always copy for now.
case "$clipboard" in
x11) printf "%s\n" "$output_url" | yank_x11;;
wayland) printf "%s\n" "$output_url" | yank_wl;;
esac
notify_cmd "ImgBB Uploader" "Success! URL copied: $output_url"
# Temporary files are removed automatically by the 'trap' command on exit if they were created.
exit 0