#!/bin/bash

# API spec: https://litterbox.catbox.moe/tools.php
# curl -F "reqtype=fileupload" -F "time=1h" -F "fileToUpload=@filename" \
#   https://litterbox.catbox.moe/resources/internals/api.php
# time can be 1h, 12h, 24h, or 72h
# The following file types are currently not allowed:
# .exe, .scr, .cpl, .doc*, .jar
# ...so this script will gzip those automatically. otherwise, use -z
# to gzip and -Z to zip.

SELF="$( basename $0 )"
APIURL="https://litterbox.catbox.moe/resources/internals/api.php"
URLS=""
EXPIRE="1"
DELAY="${DELAY:-2}"
UPLOAD_COUNT=0

die() {
  echo "$SELF: fatal: $@" 1>&2
  exit 1
}

warn() {
  echo "$SELF: warning: $@" 1>&2
}

info() {
  [ "$VERBOSE" = "1" ] && echo "$SELF: info: $@" 1>&2
}

usage() {
  cat <<EOF
$SELF: upload files to litterbox.catbox.moe

Usage: $SELF [-e hours] [-g] [-z] [-c] [-v] file <file ...>

Each file can be a filename, a directory name, or "-" for standard
input. If no files are given, $SELF reads from standard input.

Once a file is uploaded, its URL is printed on stdout.
If xsel is installed, it's also copied to the X paste buffer.

When uploading multiple files, all the URLs are copied to
the X buffer (separated by spaces). Also, a delay of $DELAY
seconds is added between uploads, to avoid hammering the site.

Options:
  -e   Set expiration time in hours. Allowed values are 1, 12,
       24, or 72. Default is $EXPIRE.

  -g   Upload gzipped files. This is automatically enabled
       when uploading files whose names end in .exe, .scr,
       .cpl, .doc*, .jar (unless -z is given). Also, directories
       are automatically tarred/gzipped (unless -z is given).

  -z   Upload zipped files or dirs.

  -t   Upload tarred and gzipped directory. Won't work with
       single files (use -g or -z for those).

  -c   Upload encrypted file. Currently this means the file
       is encrypted with 'openssl enc -pbkdf2 -aes-256-cbc', and
       you're prompted for an encryption key. Whoever downloads
       the encrypted file will have to decrypt it manually,
       e.g. with:
         openssl enc -d -pbkdf2 -aes-256-cbc < file.encrypted > file.decrypt

  -v   Verbose operation.

Author: B. Watson <urchlay@slackware.uk>
License: WTFPL (Do WTF you want with this)
EOF
}

set_expiration() {
  case "$1" in
    1|12|24|72) ;; # OK
    *) die "invalid expiration time, allowed values are: 1 12 24 72" ;;
  esac
  EXPIRE="$1"
}

add_url() {
  if [ "$URLS" = "" ]; then
    URLS="$1"
  else
    URLS+=" $1"
  fi
  : $(( UPLOAD_COUNT++ ))
}

copy_urls() {
  if ! xsel --version &>/dev/null; then
    warn "xsel not found in PATH, not copying URL(s) to clipboard"
    return
  fi

  if [ "$DISPLAY" = "" ]; then
    warn "DISPLAY not set in env, not copying URL(s) to clipboard"
    return
  fi

  echo -n "$URLS" | xsel -i
}

compress_file() {
  local ext
  local basefile
  local is_dir

  [ -d "$FILE" ] && is_dir=1

  if [ "$ZIP" = "1" ]; then
    ext=.zip
  else
    ext=.gz
  fi

  basefile="$( basename "$FILE" )"

  # TODO: create a random tmp dir, don't use /tmp!
  TMPFILE="${TMP:-/tmp}/$basefile$ext"
  rm -f "$TMPFILE"
  if [ "$ZIP" = "1" ]; then
    zip -qr "$TMPFILE" "$FILE"
    info "zipped $FILE as $TMPFILE"
  else
    if [ "$is_dir" = 1 ]; then
      tar cfz "$TMPFILE" "$FILE"
      info "tarred up $FILE as $TMPFILE"
    else
      gzip -9 < "$FILE" > "$TMPFILE"
      info "gzipped $FILE as $TMPFILE"
    fi
  fi
  FILE="$TMPFILE"
}

# TODO: curl options:
# --max-time --retry --retry-max-time
upload_file() {
  local url
  local use_compression

  info "preparing to upload $FILE"

  case "$FILE" in
    *.exe|*.scr|*.cpl|*.doc*|*.jar)
      warn "forbidden file extension, compression forced"
      use_compression=1 ;;
    *)
      use_compression="$COMPRESS" ;;
  esac

  if [ "$use_compression" = 1 ]; then
    # can't easily zip stdin, write it to a file
    if [ "$FILE" = "-" ]; then
      FILE="${TMP:-/tmp}/$SELF.stdin"
      cat > "$FILE"
      if [ "$?" != 0 ]; then
        rm -f "$FILE"
        return
      fi
    fi
    compress_file # resets FILE, sets TMPFILE
  fi

  # at this point, we have an actual file, even if input was stdin,
  # so we can encrypt it.
  if [ "$ENC" = "1" ]; then
    CRYPTFILE="$FILE.aes"
    openssl enc -pbkdf2 -aes-256-cbc < "$FILE" > "$CRYPTFILE"
    if [ "$?" != "0" ]; then
      warn "encryption failed, skipping file"
      return
    fi
    FILE="$CRYPTFILE"
  fi

  if [ "$URLS" !=  "" ]; then
    info "sleeping $DELAY sec between uploads"
    sleep "$DELAY"
  fi

  info "uploading $FILE"

  url="$(
  cat "$FILE" | \
    curl --silent \
      -F "reqtype=fileupload" \
      -F "time=${EXPIRE}h" \
      -F "fileToUpload=@-" \
      "$APIURL"
  )"
  # TODO: check curl's exit status!

  case "$url" in
    https://litter.catbox.moe/*) add_url "$url" ;;
    *) warn "upload failed" ;;
  esac
}

# main()
if [ "$1" = "--help" ]; then
  usage ; exit 0
fi

while getopts ":e:gzcv" OPT; do
  case "$OPT" in
    e) set_expiration "$OPTARG" ;;
    g) GZIP=1    ;;
    z) ZIP=1     ;;
    c) ENC=1     ;;
    v) VERBOSE=1 ;;
    *) die "invalid option -$OPTARG, try --help" ;;
  esac
done

[ "$GZIP" = 1 -a "$ZIP" = 1 ] && die "can't give both -g and -z"
[ "$GZIP" = 1 -o "$ZIP" = 1 ] && COMPRESS=1

info "expire time set to $EXPIRE hour(s)"

shift $(($OPTIND - 1))

[ "$1" = "" ] && set -- -
for arg; do
  TMPFILE=""
  CRYPTFILE=""

  if [ "$arg" = "-" -o -f "$arg" ]; then
    FILE="$arg"
  elif [ -d "$arg" ]; then
    FILE="$arg"
    compress_file # sets FILE
  else
    warn "$arg not a regular file, ignoring"
    continue
  fi

  upload_file

  if [ "$TMPFILE" != "" ]; then
    info "removing temp file $TMPFILE"
    rm -f "$TMPFILE"
  fi

  if [ "$TMPFILE" != "" ]; then
    info "removing temp encrypted file $CRYPTFILE"
    rm -f "$CRYPTFILE"
  fi
done

if [ "$UPLOAD_COUNT" = "0" ]; then
  die "no files uploaded"
fi

echo "$URLS"
copy_urls

info "$UPLOAD_COUNT file(s) uploaded"

exit 0
