#!/bin/bash # A Script that automates pasting to a number of pastebin services # relying only on bash, sed, coreutils (mktemp/sort/tr/wc/whoami) and wget # Author: Bo Ørsted Andresen, bo.andresen@zlin.dk ########################################################################## VERSION="2" ### 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' } show_url() { [[ "$(type -t add_to_clipboard)" == "function" ]] && add_to_clipboard "${1}" echo "Your ${2}paste can be seen here: ${1}" } ### defaults # The following defaults can be overridden in either /etc/wgetpaste or ~/.wgetpaste. # # 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 funcion 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}" DEFAULT_EXPIRATION="${DEFAULT_EXPIRATION:-1 month}" # setting e.g. DEFAULT_EXPIRATION_${SERVICE} can be used to override the default setting for # just one service ### 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 " -e, --expiration EXPIRATION set when it should expire (defaults to \"${DEFAULT_EXPIRATION}\")" echo echo " -S, --list-services list supported pastebin services" echo " -L, --list-languages list languages supported by the specified service" echo " -E, --list-expiration list expiration setting supported by the specified service" echo echo " -c, --command COMMAND paste COMMAND and the output of COMMAND" echo " -i, --info append the output of \`emerge --info\`" echo " -I, --info-only paste the output of \`emerge --info\` only" echo " -x, --xcut read input from clipboard (requires configuration)" echo echo " -r, --raw show url for the raw paste (no syntax highlighting or html)" 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 (DEFAULT_{NICK,SERVICE,LANGUAGE,EXPIRATION}[_\${SERVICE}]) can be" echo "overridden globally in /etc/wgetpaste or per user in ~/.wgetpaste." } ### services SERVICES=(ca rafb osl sh) SERVICE_URLS=(http://pastebin.ca http://rafb.net/paste/ http://pastebin.osuosl.org/ http://sh.nu/p/) SERVICE_URLS_RAW=(http://pastebin.ca http://rafb.net/paste/paste.php http://pastebin.osuosl.org/pastebin.php http://sh.nu/p/) # 4 (base indentation) + max service length + 2 (space + dash) INDENTATION=10 show_services() { echo 'Services supported (case sensitive):' for ((i=0; i<${#SERVICES[*]}; i++)); do echo " ${SERVICES[i]} "$'\e'"[${INDENTATION}G- ${SERVICE_URLS[i]}" done } verify_service() { for service in ${SERVICES[*]}; do [[ "$*" == "${service}" ]] && return 0 done fail "$*" "service" "show_services" } show() { for var in $(eval "echo \${${SERVICE}_${1}[*]}"); do echo " ${var//_/ }" done } verify() { for ((i=0; i<$(eval echo \${#${SERVICE}_${1}S[*]}); i++)); do if [[ "$(eval "echo \${${1}}")" == "$(eval "echo \${${SERVICE}_${1}S[i]//_/ }")" ]]; then case "${2}" in count ) eval "${1}=\"${i}\"" ;; value ) eval "${1}=\${${SERVICE}_${1}_VALUES[i]}" ;; esac return 0 fi done return 1 } ### 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) # osl - ordering for languages and values must be the same osl_LANGUAGES=(Plain_Text ActionScript Ada Apache_Log_File AppleScript Assembly_\(NASM\) \ ASP Bash C C_for_Macs CAD_DCL CAD_Lisp C++ C\# ColdFusion CSS D Delphi Diff DOS Eiffel Fortran \ FreeBasic Game_Maker HTML INI_file Java Javascript Lisp Lua MatLab Microprocessor_ASM MySQL \ NullSoft_Installer Objective_C OCaml Openoffice\.org_BASIC Oracle_8 Pascal Perl PHP Python \ QBasic\/QuickBASIC Robots Ruby Scheme Smarty SQL TCL VB VB\.NET VisualFoxPro XML) osl_LANGUAGE_VALUES=(text actionscript ada apache applescript asm asp bash c c_mac caddcl \ cadlisp cpp csharp cfm css d delphi diff dos eiffel fortran freebasic gml html4strict ini java \ javascript lisp lua matlab mpasm mysql nsis objc ocaml oobas oracle8 pascal perl php python \ qbasic robots ruby scheme smarty sql tcl vb vbnet visualfoxpro xml) show_languages() { echo "Languages supported by ${SERVICE}: $(get_recipient) (case sensitive):" case "${SERVICE}" in ca | osl | rafb ) show LANGUAGES | 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 ) verify LANGUAGE count && return 0 ;; rafb ) verify LANGUAGE && return 0 ;; osl ) verify LANGUAGE value && return 0 ;; * ) [[ ! ${LANGUAGE_SET} ]] && return 0 ;; esac fail "${LANGUAGE}" "language" "show_languages" } ### expiration # ca ca_EXPIRATIONS=(Never 5_minutes 10_minutes 15_minutes 30_minutes 45_minutes 1_hour 2_hours 4_hours \ 8_hours 12_hours 1_day 2_days 3_days 1_week 2_weeks 3_weeks 1_month 2_months 3_months 4_months \ 5_months 6_months 1_year) # osl - ordering for options and values must be the same osl_EXPIRATIONS=(Never 1_day 1_month) osl_EXPIRATION_VALUES=(f d m) show_expiration_options() { echo "Expiration options supported by ${SERVICE}: $(get_recipient) (case sensisitive):" case "${SERVICE}" in ca | osl ) show EXPIRATIONS ;; rafb ) echo 1>&2 echo "Pastes on ${SERVICE}: $(get_recipient) expires after 24 hours." 1>&2 echo "${SERVICE} has no suppport for setting expiration." 1>&2 ;; * ) echo 1>&2 echo "${SERVICE} has no suppport for setting expiration." 1>&2 ;; esac } verify_expiration_options() { case "${SERVICE}" in ca ) verify EXPIRATION && return 0 ;; osl ) verify EXPIRATION value && return 0 ;; * ) [[ ! ${EXPIRATION_SET} ]] && return 0 ;; esac fail "${EXPIRATION}" "expiration option" "show_expiration_options" } ### Posting helper functions # get the url to post to for any given service get_recipient() { for ((i=0; i<${#SERVICES[*]}; i++)); do [[ "${SERVICE}" == "${SERVICES[i]}" ]] && eval "echo \"\${SERVICE_URLS${1}[i]}\"" && return 0 done die "Failed to get url for \"${SERVICE}\"." } # 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. note that this is only a warning printed. it doesn't abort or anything. warn_size() { warn() { if [[ ${SIZE} -gt ${1} ]]; then echo "Pasting > ${2} 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." fi } case "${SERVICE}" in rafb ) warn 512000 "512 kb" ;; ca ) warn 1024000 "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}" ;; osl ) echo "poster=${NICK}&format=${LANGUAGE}&expiry=${EXPIRATION}&paste=Send&code2=${INPUT}" ;; rafb ) echo "nick=${NICK}&lang=${LANGUAGE}&desc=${DESCRIPTION}&cvt_tabs=${CVT_TABS}&text=${INPUT}" ;; sh ) echo "poster=${NICK}&code=${INPUT}" ;; * ) die "\"${SERVICE}\" is not supported by ${FUNCNAME}()." ;; esac } # indicate if ${SERVICE} needs stdout output from wget to get the resulting url need_stdout() { case "${SERVICE}" in ca ) return 0 ;; osl | rafb | sh ) return 1 ;; * ) die "\"${SERVICE}\" is not supported by ${FUNCNAME}()." ;; esac } # get the url get_url() { case "${SERVICE}" in ca ) echo "$*" | sed -n 's|^.*content="[0-9]*;\(http://pastebin.ca/[0-9]*\)".*$|\1|p' ;; osl | rafb | sh ) echo "$*" | sed -n 's|^.*Location:\ \(http://[^\ ]\+\).*$|\1|p' ;; * ) die "\"${SERVICE}\" is not supported by ${FUNCNAME}()." ;; esac } # verify that the pastebin service didn't return a known error url or print a helpful error message verify_url() { if [[ "${SERVICE}" == "rafb" ]] && [[ "${URL}" == "http://rafb.net/p/toofast.html" ]]; then die "You must wait at least 10 seconds between each paste! Try again in 10 seconds." fi } # if possible convert URL to raw convert_to_raw() { case "${SERVICE}" in ca ) RAW_URL="$(echo "${URL}" | sed -e 's|^\(http://pastebin.ca/\)\(.*\)$|\1raw/\2|')" ;; osl ) RAW_URL="$(echo "${URL}" | sed -e 's|^\(http://pastebin.osuosl.org/\)\(.*\)$|\1pastebin.php?dl=\2|')" ;; rafb ) RAW_URL="$(echo "${URL}" | sed -e 's|html\?$|txt|')" ;; * ) echo "Raw download of pastes is not supported by ${SERVICE}: $(get_recipient)." 1>&2 return 1 ;; esac return 0 } ### read cli options # convert groups of short options to singular short options. convert long options to short options. while [[ -n "${1}" ]]; do case "${1}" in -- ) for arg in "${@}"; do ARGS[${#ARGS[*]}]="${arg}" done break ;; --*=* ) ARGS[${#ARGS[*]}]="${1%=*}" ARGS[${#ARGS[*]}]="${1#*=}" ;; --* ) ARGS[${#ARGS[*]}]="${1}" ;; -* ) for short_arg in $(echo "${1#-}" | sed 's|.| -&|g'); do ARGS[${#ARGS[*]}]="${short_arg}" done ;; * ) ARGS[${#ARGS[*]}]="${1}" esac shift done # set the converted options as input set -- "${ARGS[@]}" no_argument() { die "${0}: option requires an argument ${1}" } get_filename() { for ((i=0; i<${#}; i++)); do if [[ -f "${1}" ]]; then SOURCE="files" FILES[${#FILES[*]}]="${1}" else die "${0}: ${1}: No such file found. " fi done } while [[ ! -z "${1}" ]]; do case "${1}" in -c | --command ) [[ -z "${2}" ]] && no_argument "${1}" SOURCE="command" COMMANDS[${#COMMANDS[*]}]="${2}" shift 2 ;; --debug ) DEBUG=true set -x shift ;; -d | --description ) [[ -z "${2}" ]] && no_argument "${1}" DESCRIPTION="${2}" shift 2 ;; -e | --expiration ) [[ -z "${2}" ]] && no_argument "${1}" EXPIRATION_SET=true EXPIRATION="${2}" shift 2 ;; -E | --list-expiration ) LIST_EXPIRATION=true shift ;; -h | --help ) show_usage && exit 0 ;; -i | --info ) INFO=true shift ;; -I | --info-only ) SOURCE="info" shift ;; -l | --language ) [[ -z "${2}" ]] && no_argument "${1}" LANGUAGE_SET=true LANGUAGE="${2}" shift 2 ;; -L | --list-languages ) LIST_LANGUAGES=true shift ;; -n | --nick ) [[ -z "${2}" ]] && no_argument "${1}" NICK="$(escape "${2}")" shift 2 ;; -r | --raw ) RAW=true shift ;; -s | --service ) [[ -z "${2}" ]] && no_argument "${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 ) SOURCE="xcut" shift ;; -- ) shift && get_filename "${@}" && break ;; *) get_filename "${1}" && shift ;; 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}}" [[ -n "$(eval "echo \${DEFAULT_NICK_${SERVICE}}")" ]] && NICK="${NICK:-$(eval "echo \${DEFAULT_NICK_${SERVICE}}")}" NICK="${NICK:-$(escape "${DEFAULT_NICK}")}" [[ -z "${SOURCE}" ]] && SOURCE="stdin" && FILES[${#FILES[*]}]="/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 # show expiration options if requested (needs to be done after the right service is selected) [[ ${LIST_EXPIRATION} ]] && show_expiration_options && exit 0 # language needs to be verified before it is escaped but after service is selected [[ -n "$(eval "echo \${DEFAULT_LANGUAGE_${SERVICE}}")" ]] && LANGUAGE="${LANGUAGE:-$(eval "echo \${DEFAULT_LANGUAGE_${SERVICE}}")}" LANGUAGE="${LANGUAGE:-${DEFAULT_LANGUAGE}}" # uses ${SERVICE} and ${LANGUAGE}. may change the value of the latter. verify_language LANGUAGE="$(escape "${LANGUAGE}")" # expiration needs to be verified before it is escaped but after service is selected [[ -n "$(eval "echo \${DEFAULT_EXPIRATION_${SERVICE}}")" ]] && EXPIRATION="${EXPIRATION:-$(eval "echo \${DEFAULT_EXPIRATION_${SERVICE}}")}" EXPIRATION="${EXPIRATION:-${DEFAULT_EXPIRATION}}" # uses ${SERVICE} and ${EXPIRATION}. may change the value of the latter. verify_expiration_options EXPIRATION="$(escape "${EXPIRATION}")" # set prompt if [[ ${UID} == 0 ]]; then PS1="#" else PS1="$" fi # set default description if [[ -z "${DESCRIPTION}" ]]; then case "${SOURCE}" in info ) DESCRIPTION="${PS1} emerge --info;" ;; command ) DESCRIPTION="${PS1}" for ((i=0 ; i<${#COMMANDS[*]}; i++)); do DESCRIPTION="${DESCRIPTION} ${COMMANDS[i]};" done ;; files ) DESCRIPTION="${FILES[*]}" ;; * ) DESCRIPTION="${SOURCE}" ;; esac fi # read input case "${SOURCE}" in command ) for ((i=0 ; i<${#COMMANDS[*]}; i++)); do INPUT="${INPUT}${PS1} ${COMMANDS[i]}"$'\n'"$(bash -c "${COMMANDS[i]}" 2>&1)"$'\n\n' done ;; info ) INPUT="${PS1} emerge --info"$'\n'"$(emerge --info --ignore-default-opts)" ;; xcut ) if [[ "$(type -t get_from_clipboard)" == "function" ]]; then INPUT="$(get_from_clipboard)" else echo "You need to define get_from_clipboard() in /etc/wgetpaste or ~/.wgetpaste to use" 1>&2 echo "--xcut. If you want to use e.g. xclip simply emerge xclip and define it like this:" 1>&2 echo -e "\nget_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 -e "\nadd_to_clipboard() {\n xclip \"\$*\"\n}\n" 1>&2 echo "You may use whatever your window manager provides to alter your clipboard instead" 1>&2 echo "of xclip." 1>&2 exit 1 fi ;; stdin | files ) # handle the case where the input source (defaulting to /dev/stdin) isn't readable verbosely if [[ ${#FILES[*]} -gt 1 ]]; then for ((i=0; i<${#FILES[*]}; i++)); do file="${FILES[i]}" if [[ ! -r "${file}" ]]; then die "The input source: \"${file}\" is not readable. Please specify a readable input source." fi INPUT="${INPUT}${PS1} cat ${file}"$'\n'"$( < "${file}" )"$'\n\n' done else INPUT="$( < "${FILES}" )" fi ;; esac [[ -z "${INPUT}" ]] && die "No input read. Nothing to paste. Aborting." # append emerge --info if needed if [[ ${INFO} ]]; then DESCRIPTION="${DESCRIPTION} ${PS1} emerge --info;" INPUT="${INPUT}"$'\n'"${PS1} emerge --info"$'\n'"$(emerge --info --ignore-default-opts)" fi # escape DESCRIPTION and INPUT DESCRIPTION="$(escape "${DESCRIPTION}")" INPUT="$(escape "${INPUT}")" # 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)" if [[ -n "${TEMPFILE}" ]] && [[ -f "${TEMPFILE}" ]]; then # write paste data to the temporary file post_data > "${TEMPFILE}" || die "Failed to write to temporary file: \"${TEMPFILE}\"." WGET_ARGS="--post-file=${TEMPFILE}" else # fall back to using --post-data if the temporary file could not be created # TABs and new lines need to be escaped for wget to interpret it as one string WGET_ARGS="--post-data=$(post_data | sed -e 's|$|%0a|g' -e 's|\t|%09|g' | tr -d '\n')" fi # set recipient RECIPIENT="$(get_recipient "_RAW")" # paste it WGET_ARGS="--tries=5 --timeout=60 ${WGET_ARGS}" 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 it was created if [[ -n "${TEMPFILE}" ]] && [[ -f "${TEMPFILE}" ]]; then 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 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 die "Apparently nothing was received. Perhaps the connection failed."$'\n'"${OUTPUT}" else echo "Apparently nothing was received. Perhaps the connection failed. Enable --verbose or" 1>&2 die "--debug to get the output from wget that can help diagnose it correctly." fi fi # convert_to_raw() may change the value of RAW. Otherwise it set RAW_URL. if [[ ${RAW} ]] && convert_to_raw; then show_url "${RAW_URL}" "raw " else show_url "${URL}" fi exit 0