#!/bin/bash # A Script that automates pasting to a number of pastebin services # relying only on bash v3, sed, coreutils (mktemp/sort/wc/whoami) and wget # Author: Bo Ørsted Andresen, bo.andresen@zlin.dk ########################################################################## VERSION="1.9" ### helper functions # show an error message and die die() { echo "$*" 1>&2 exit 1 } # show that an option ${1} is not supported, run function that shows valid options ${3} and die fail() { if [[ "${2}" == "service" ]]; then echo "\"$1\" is not a supported $2." 1>&2 else echo "\"$1\" is not a supported $2 for ${SERVICE}: $(get_recipient)." 1>&2 fi echo 1>&2 ${3} 1>&2 exit 1 } # escape % (used for escaping), & (used as separator in POST data), + (used as space in POST data) and space escape() { echo "$*" | sed -e 's|%|%25|g' -e 's|&|%26|g' -e 's|+|%2b|g' -e 's| |+|g' } ### defaults # The following defaults can be overridden in either /etc/wgetpaste or ~/.wgetpaste. # Only those four variables should be set in those files. # # If add_to_clipboard() is defined as a function in one of those files it will be called with # the url where your paste can be seen as an argument. You may use xclip, xcut, klipper or # whatever your window manager provides for adding it to your clipboard. # # Likewise if get_from_clipboard() is defined as a funciont in one of those files it will be # called to retrieve input from your clipboard when --xcut is used. [[ -f /etc/wgetpaste ]] && . /etc/wgetpaste [[ -f ~/.wgetpaste ]] && . ~/.wgetpaste DEFAULT_NICK="${DEFAULT_NICK:-$(whoami)}" DEFAULT_SERVICE="${DEFAULT_SERVICE:-rafb}" DEFAULT_LANGUAGE="${DEFAULT_LANGUAGE:-Plain Text}" ### usage show_usage() { echo "Usage: $0 [options] [file]" echo echo "Options:" echo " -l, --language LANG set language (defaults to \"${DEFAULT_LANGUAGE}\")" echo " -d, --description DESCRIPTION set description (defaults to \"stdin\" or filename)" echo " -n, --nick NICK set nick (defaults to your username))" echo " -s, --service SERVICE set service to use (defaults to \"${DEFAULT_SERVICE}\")" echo echo " -S, --list-services list supported pastebin services" echo " -L, --list-languages list languages supported by the specified service" echo echo " -x, --xcut read input from clipboard (requires configuration)" echo echo " -v, --verbose show wget stderr output if no url is received" echo " --debug be *very* verbose (implies -v)" echo echo " -h, --help show this help" echo " --version show version information" echo echo "Defaults can be overridden globally in /etc/wgetpaste or per user in ~/.wgetpaste." echo "Only the vars DEFAULT_{NICK,SERVICE,LANGUAGE} should be set in those files." } ### services SERVICES=(ca rafb sh) SERVICE_URLS=(http://pastebin.ca http://rafb.net/paste/paste.php http://sh.nu/p/) # 4 (base indentation) + max service length + 2 (space + dash) INDENTATION=10 show_services() { echo 'Services supported (case sensitive):' for index in ${!SERVICES[*]}; do echo " ${SERVICES[index]} "$'\e'"[${INDENTATION}G- ${SERVICE_URLS[index]}" done } verify_service() { for index in ${!SERVICES[*]}; do [[ "$*" == "${SERVICES[index]}" ]] && return 0 done fail "$*" "service" "show_services" } ### languages # rafb rafb_LANGUAGES=(C C89 C99 C++ C\# Java Pascal Perl PHP PL\/I Python Ruby SQL VB Plain\ Text) # ca - ordering is important here as their value is set as their number in the order ca_LANGUAGES=(Plain\ Text Asterisk\ Configuration C C++ PHP Perl Java VB C\# Ruby \ Python Pascal mIRC PL/I XML SQL Scheme ActionScript Ada \ Apache\ Configuration Assembly\ \(NASM\) ASP Bash CSS Delphi HTML\ 6\.0\ Strict JavaScript \ LISP Lua Microprocessor\ ASM Objective\ C VB\.NET) show_languages() { echo "Languages supported by ${SERVICE}: $(get_recipient) (case sensitive):" case "${SERVICE}" in ca | rafb ) for index in $(eval "echo \${!${SERVICE}_LANGUAGES[*]}"); do echo " $(eval "echo \${${SERVICE}_LANGUAGES[index]}")" done | sort ;; * ) echo 1>&2 echo "\"${SERVICE}\" has no support for any specific languages." 1>&2 esac } # this is in place because rafb.net (probably others too) rejects any paste with an invalid language verify_language() { case "${SERVICE}" in ca ) let "i = 0" for index in ${!ca_LANGUAGES[*]}; do let "i = i +1" [[ "${LANGUAGE}" == "${ca_LANGUAGES[index]}" ]] && LANGUAGE="${i}" && return 0 done ;; rafb ) for index in ${!rafb_LANGUAGES[*]}; do [[ "${LANGUAGE}" == "${rafb_LANGUAGES[index]}" ]] && return 0 done ;; * ) [[ ! ${LANGUAGE_SET} ]] && return 0 ;; esac fail "${LANGUAGE}" "language" "show_languages" } ### Posting helper functions # get the url to post to for any given service get_recipient() { for index in ${!SERVICES[*]}; do [[ "${SERVICE}" == "${SERVICES[index]}" ]] && echo "${SERVICE_URLS[index]}" && return 0 done echo "Failed to get url for \"${SERVICE}\"." 1>&2 exit 1 } # print a warning if failure is predictable due to the mere size of the paste. sh seems to be the most reliable # service in that regard. for now no warning will be printed for that. note that this is only a warning printed. # it doesn't abort or anything. warn_size() { print_warning() { echo "Pasting > ${1} often tend to fail with ${SERVICE}. Use --verbose or --debug to see the" echo "error output from wget if it fails. Alternatively use another pastebin service like e.g. sh." } case "${SERVICE}" in rafb ) [[ ${SIZE} -gt 512000 ]] && print_warning "512 kb" ;; ca ) [[ ${SIZE} -gt 1024000 ]] && print_warning "1 MB" ;; esac } # POST data post_data() { case "${SERVICE}" in ca ) echo "name=${NICK}&type=${LANGUAGE}&description=${DESCRIPTION}&expiry=${EXPIRATION}&s=Submit+Post&content=${INPUT}" ;; rafb ) echo "nick=${NICK}&lang=${LANGUAGE}&desc=${DESCRIPTION}&cvt_tabs=${CVT_TABS}&text=${INPUT}" ;; sh ) echo "poster=${NICK}&code=${INPUT}" ;; * ) echo "\"${SERVICE}\" is not supported by ${FUNCNAME}()." 1>&2 exit 1 ;; esac } # indicate if ${SERVICE} needs stdout output from wget to get the resulting url need_stdout() { case "${SERVICE}" in # this service needs to use a regex to retrieve the url from stdout ca ) return 0 ;; # these services read the reply from Location: which goes to stderr rafb | sh ) return 1 ;; * ) echo "\"${SERVICE}\" is not supported by ${FUNCNAME}()." 1>&2 exit 1 ;; esac } # get the url get_url() { case "${SERVICE}" in ca ) echo "$*" | sed -n 's|^.*content="[0-9]*;\(http://pastebin.ca/[0-9]*\)".*$|\1|p' ;; rafb | sh ) echo "$*" | sed -n 's|^.*Location:\ \(http://[^\ ]\+\).*$|\1|p' ;; * ) echo "\"${SERVICE}\" is not supported by ${FUNCNAME}()." 1>&2 exit 1 ;; esac } # verify that the pastebin service didn't return a known error url such as toofast.html from rafb verify_url() { case "${SERVICE}" in rafb ) if [[ "${URL}" == "http://rafb.net/p/toofast.html" ]]; then echo "You must wait at least 10 seconds between each paste! Try again in 10 seconds." 1>&2 exit 1 fi ;; esac } ### read cli options # if you know a better option (like getopts) which provides the same features # please let me know how. while [[ ! -z "${1}" ]]; do case "${1}" in --debug ) DEBUG=true set -x shift ;; --description=* ) [[ -z "${1#*=}" ]] && show_usage 1>&2 && exit 1 DESCRIPTION="$(escape "${1#*=}")" shift ;; -d | --description ) [[ -z "${2}" ]] && show_usage 1>&2 && exit 1 DESCRIPTION="$(escape "${2}")" shift 2 ;; -h | --help ) show_usage exit 0 ;; --language=* ) [[ -z "${1#*=}" ]] && show_usage 1>&2 && exit 1 LANGUAGE_SET=true LANGUAGE="${1#*=}" shift ;; -l | --language ) [[ -z "${2}" ]] && show_usage 1>&2 && exit 1 LANGUAGE_SET=true LANGUAGE="${2}" shift 2 ;; -L | --list-languages ) LIST_LANGUAGES=true shift ;; --nick=* ) [[ -z "${1#*=}" ]] && show_usage 1>&2 && exit 1 NICK="$(escape "${1#*=}")" shift ;; -n | --nick ) [[ -z "${2}" ]] && show_usage 1>&2 && exit 1 NICK="$(escape "${2}")" shift 2 ;; --service=* ) [[ -z "${1#*=}" ]] && show_usage 1>&2 && exit 1 verify_service "${1#*=}" SERVICE="$(escape "${1#*=}")" shift ;; -s | --service ) [[ -z "${2}" ]] && show_usage 1>&2 && exit 1 verify_service "${2}" SERVICE="$(escape "${2}")" shift 2 ;; -S | --list-services ) show_services exit 0 ;; -v | --verbose ) VERBOSE=true shift ;; --version ) echo "$0, version ${VERSION}" exit 0 ;; -x | --xcut ) XCUT=true SOURCE="xcut" shift ;; *) if [[ -f "${1}" ]]; then SOURCE="${1}" shift else show_usage 1>&2 exit 1 fi ;; esac done ### everything below this should be independent of which service is being used... # set default service, nick, source and tabs convertion SERVICE="${SERVICE:-${DEFAULT_SERVICE}}" NICK="${NICK:-$(escape "${DEFAULT_NICK}")}" SOURCE="${SOURCE:-/dev/stdin}" CVT_TABS="No" # show languages if requested (needs to be done after the right service is selected) [[ ${LIST_LANGUAGES} ]] && show_languages && exit 0 # language needs to be verified before it is escaped but after service is selected LANGUAGE="${LANGUAGE:-${DEFAULT_LANGUAGE}}" verify_language LANGUAGE="$(escape "${LANGUAGE}")" # set default description if [[ -z "${DESCRIPTION}" ]]; then if [[ "${SOURCE}" == "/dev/stdin" ]]; then DESCRIPTION="stdin" else DESCRIPTION="$(escape "${SOURCE}")" fi fi # handle the case where the input source (defaulting to /dev/stdin) isn't readable verbosely if [[ ! ${XCUT} ]] && [[ ! -r "${SOURCE}" ]]; then echo "The input source: \"${SOURCE}\" is not readable. Please specify a readable input source." 1>&2 echo 1>&2 show_usage 1>&2 exit 1 fi # read input if [[ ${XCUT} ]]; then if [[ "$(type -t get_from_clipboard)" == "function" ]]; then INPUT="$(escape "$(get_from_clipboard)")" else echo "You need to define get_from_clipboard() in /etc/wgetpaste or ~/.wgetpaste to use --xcut." 1>&2 echo "If you want to use e.g. xclip simply emerge xclip and define it like this:" 1>&2 echo 1>&2 echo -e "get_from_clipboard() {\n xclip -o\n}\n" 1>&2 echo "Likewise if you want the resulting url stored in your clipboard using e.g. xclip" 1>&2 echo "define it like this:" 1>&2 echo 1>&2 echo -e "add_to_clipboard() {\n xclip \"\$*\"\n}\n" 1>&2 echo "Use whatever your window manager provides to alter your clipboard." 1>&2 exit 1 fi else INPUT="$(escape "$( < "${SOURCE}" )")" fi [[ -z "${INPUT}" ]] && die "No input read. Nothing to paste. Aborting." # print a friendly warning if the size makes failure predictable for the specified pastebin service. SIZE=$(echo "${INPUT}" | wc -c) warn_size 1>&2 # create temp file (wget is much more reliable reading large input from a file than from the cli directly TEMPFILE="$(mktemp /tmp/wgetpaste.XXXXXX)" [[ -f "${TEMPFILE}" ]] || die "Failed to create a temporary file." 1>&2 # write paste data to the temporary file post_data > "${TEMPFILE}" || die "Failed to write to temporary file: \"${TEMPFILE}\"." # set recipient RECIPIENT="$(get_recipient)" # paste it WGET_ARGS="--tries=5 --timeout=60 --post-file=${TEMPFILE}" if ! need_stdout && [[ ! ${DEBUG} ]] && [[ -w /dev/null ]]; then OUTPUT="$(wget -O /dev/null ${WGET_ARGS} ${RECIPIENT} 2>&1)" else OUTPUT="$(wget -O - ${WGET_ARGS} ${RECIPIENT} 2>&1)" fi # clean temporary file if [[ ! ${DEBUG} ]]; then rm "${TEMPFILE}" || echo "Failed to remove temporary file: \"${TEMPFILE}\"." 1>&2 else echo "Left temporary file: \"${TEMPFILE}\" alone for debugging purposes." fi # get the url URL="$(get_url "${OUTPUT}")" # verify that the pastebin service didn't return a known error url such as toofast.html from rafb # uses ${SERVICE} and ${URL}. verify_url # handle the case when there was no location returned if [[ -z "${URL}" ]]; then if [[ ${DEBUG} ]] || [[ ${VERBOSE} ]]; then echo "Apparently nothing was received. Perhaps the connection failed." 1>&2 echo "${OUTPUT}" 1>&2 else echo "Apparently nothing was received. Perhaps the connection failed. Enable --verbose or" 1>&2 echo "--debug to get the output from wget that can help diagnose it correctly." 1>&2 fi exit 1 fi # add_to_clipboard() may be defined in /etc/wgetpaste or ~/.wgetpaste and can be used to add # ${URL} to your clipboard using xclip, xcut, klipper or whatever your window manager provides # for that task. [[ "$(type -t add_to_clipboard)" == "function" ]] && add_to_clipboard "${URL}" echo "Your paste can be seen here: ${URL}" exit 0