diff --git a/README.md b/README.md index 4a33735..7a11561 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ #pushbullet-bash Created by Red5d - https://github.com/Red5d -This is a Bash interface to the PushBullet API (https://www.pushbullet.com/). +This is a shell/CLI interface to the PushBullet API (https://www.pushbullet.com/). It can list your available devices and push different types of data to them. @@ -15,6 +15,4 @@ PB_CONFIG=~/.my-config pushbullet list PB_API_KEY=1234 pushbullet list ``` -This is intended to be a pure Bash utility with no non-Bash dependencies. - Pushbullet-bash also features a shell function similar to projects like [pushblast](https://github.com/alebcay/pushblast), [pushbullet-exit](https://github.com/rfilmyer/pushbullet-exit) and [pace](https://github.com/esamson/pace) to send a message after a specific command has finished. diff --git a/pushbullet b/pushbullet index 8a46b78..abcac11 100755 --- a/pushbullet +++ b/pushbullet @@ -1,4 +1,4 @@ -#! /bin/bash +#! /bin/sh # Bash interface to the PushBullet api. # Author: Red5d - https://github.com/Red5d @@ -7,14 +7,27 @@ API_URL=https://api.pushbullet.com/v2 PROGDIR="$(cd "$( dirname "$0" )" && pwd )" unset QUIET +ERR_NONE=0 +ERR_UNK=200 +ERR_DEP=201 +ERR_CFG=202 +ERR_FMT=203 +ERR_NOFILE=204 +ERR_PB_AUTH=210 +ERR_PB_DEVICE=211 +ERR_PB_OBJNOTFOUND=212 +ERR_PB_OTHER=213 + +CURLOPTS="--silent" + info() { - if [[ -z ${QUIET} ]]; then + if [ -z "${QUIET}" ]; then echo "$@" fi } err() { - if [[ -w /dev/stderr ]]; then + if [ -w /dev/stderr ]; then echo "$@" > /dev/stderr else # /dev/stderr does not exist or is not writable @@ -24,25 +37,25 @@ err() { if [ ! "$(which curl)" ]; then err "pushbullet-bash requires curl to run. Please install curl." - exit 1 + exit ${ERR_DEP} fi if [ ! -e "$PROGDIR"/JSON.sh ]; then err "Not all required libraries are installed. Please change into the pushbullet-bash folder and run the following command:" err "git submodule init && git submodule update" - exit + exit ${ERR_DEP} fi # use default PB_CONFIG if no different file or API key has been given -if [[ ! -n "$PB_CONFIG" ]] && [[ ! -n "$PB_API_KEY" ]]; then +if [ ! -n "$PB_CONFIG" ] && [ ! -n "$PB_API_KEY" ]; then PB_CONFIG=~/.config/pushbullet fi -source $PB_CONFIG > /dev/null 2>&1 +. $PB_CONFIG > /dev/null 2>&1 # don't give warning when script is called with setup option -if [[ -z "$PB_API_KEY" ]] && [[ "$1" != "setup" ]]; then +if [ -z "$PB_API_KEY" ] && [ "$1" != "setup" ]; then err -e "\e[0;33mWarning, your API key is not set.\nPlease create \"$PB_CONFIG\" with a line starting with PB_API_KEY= and your PushBullet key\e[00m" - exit 1 + exit ${ERR_CFG} fi # from here on set -e to avoid "EXPECTED value GOT EOF" errors from JSON.sh when no pushes are returned @@ -84,10 +97,10 @@ Type Parameters: \"file\" type: give the path to the file and an optional message body. Hint: The message body can also be given via stdin, leaving the message parameter empty. " -exit 1 +exit ${ERR_NONE} } -function getactivepushes () { +getactivepushes () { # first command in a function reads stdin allpushes=$("$PROGDIR"/JSON.sh -b) activepushes=$(echo "$allpushes" | egrep "\"pushes\",[0-9]+,\"active\"\].*true" \ @@ -106,28 +119,57 @@ function getactivepushes () { done } +checkCurlReturnCode() { + errcode=${1:-$ERR_UNK} + if [ "${errcode}" = "${ERR_UNK}" ]; then + err "Unknown error (${errcode})" + exit ${errcode} + elif [ "${1}" != "0" ]; then + err "Unknown error from curl (${1})" + exit ${1} + fi +} + checkCurlOutput() { res=$(echo "$1" | grep -o "created" | tail -n1) - if [[ "$1" == *"The param 'channel_tag' has an invalid value."* ]] && [[ "$1" == *"The param 'device_iden' has an invalid value."* ]]; then - err "Error: You specified an unknown device or channel." - exit 1 - elif [[ "$1" == *"invalid_access_token"* ]]; then - err "Access token is missing or invalid." - exit 1 - elif [[ "$1" == *"Object not found"* ]]; then - err "Object not found" - exit 1 - elif [[ "$1" == '{"accounts":[],"blocks":[],"channels":[],"chats":[],"clients":[],"contacts":[],"devices":[],"grants":[],"pushes":[],"profiles":[],"subscriptions":[],"texts":[]}' ]]; then - # "empty" response - return 0 - elif [[ "$1" == '{"accounts":[],"blocks":[],"channels":[],"chats":[],"clients":[],"contacts":[],"devices":[],"grants":[],"pushes":[],"profiles":[],"subscriptions":[],"texts":[],"cursor":'* ]]; then - # only next cursor, otherwise empty response - # suppress error when calling break outside of loop - break 2&> /dev/null - elif [[ "$res" != "created" ]] && [[ ! "$1" == "{}" ]]; then - err "Error submitting the request. The error message was:" "$1" - exit 1 - fi + curlretcode=${2:-$ERR_UNK} + checkCurlReturnCode "$curlretcode" + case "$1" in + *"The param 'channel_tag' has an invalid value."*) + case "$1" in + *"The param 'device_iden' has an invalid value."*) + err "Error: You specified an unknown device or channel." + exit ${ERR_PB_DEVICE} + ;; + esac + ;; + + *"invalid_access_token"*) + err "Access token is missing or invalid." + exit ${ERR_PB_AUTH} + ;; + *"Object not found"*) + err "Object not found" + exit ${ERR_PB_OBJNOTFOUND} + ;; + '{"accounts":[],"blocks":[],"channels":[],"chats":[],"clients":[],"contacts":[],"devices":[],"grants":[],"pushes":[],"profiles":[],"subscriptions":[],"texts":[]}') + # "empty" response + return 0 + ;; + '{"accounts":[],"blocks":[],"channels":[],"chats":[],"clients":[],"contacts":[],"devices":[],"grants":[],"pushes":[],"profiles":[],"subscriptions":[],"texts":[],"cursor":'*) + # only next cursor, otherwise empty response + # suppress error when calling break outside of loop + break > /dev/null 2>&1 + ;; + '{}') + ;; + *) + if [ "$res" != "created" ]; then + err "Error submitting the request. The error message was:" "$1" + exit ${ERR_PB_OTHER} + fi + ;; + esac } # function prints the cursor function if another page is needed @@ -137,17 +179,25 @@ checkPagination() { } getChats() { - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ "$API_URL/chats") + curlretcode=$? + set -e + checkCurlReturnCode "$curlretcode" # check if query needs pagination chats=$curlres cursor=$(checkPagination "$curlres") until [ -z $cursor ]; do - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --data-urlencode cursor=$cursor \ --get \ "$API_URL/chats") + curlretcode=$? + set -e + checkCurlReturnCode "$curlretcode" chats="$chats $curlres" cursor=$(checkPagination "$curlres") done @@ -155,20 +205,27 @@ getChats() { } getDevices() { - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ "$API_URL/devices") + curlretcode=$? + set -e # fail early on if token is invalid - checkCurlOutput "$curlres" + checkCurlOutput "$curlres" "$curlretcode" # check if query needs pagination devices=$curlres cursor=$(checkPagination "$curlres") until [ -z $cursor ]; do - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --data-urlencode cursor=$cursor \ --get \ "$API_URL/devices") + curlretcode=$? + set -e + checkCurlReturnCode "$curlretcode" devices="$devices $curlres" cursor=$(checkPagination "$curlres") done @@ -204,25 +261,31 @@ getPushes() { iden="/$iden" fi - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --data-urlencode active="$active" \ --data-urlencode modified_after="$modified" \ --get \ "$API_URL/pushes$iden") - checkCurlOutput "$curlres" + curlretcode=$? + set -e + checkCurlOutput "$curlres" "$curlretcode" response="[ $curlres" # check if query needs pagination until [ -z "$(checkPagination "$curlres")" ]; do - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --data-urlencode active="$active" \ --data-urlencode modified_after="$modified" \ --data-urlencode cursor="$(checkPagination "$curlres")" \ --get \ "$API_URL/pushes$iden") + curlretcode=$? + set -e response="$response, $curlres" # checkCurlOutput will call break once no new pushes are received - checkCurlOutput "$curlres" + checkCurlOutput "$curlres" "$curlretcode" done echo "$response ]" } @@ -243,14 +306,18 @@ create-device) # create a device in pushbullet named like the current hostname if [ ! -z $("$0" list | grep -Fx "$(hostname)") ]; then err "A device already exists with your hostname" - exit 1 + exit ${ERR_PB_DEVICE} fi - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --header 'Content-Type: application/json' \ --data-binary "{\"nickname\":\"$(hostname)\",\"model\":\"Created by pushbullet-bash\",\"icon\":\"system\"}" \ --request POST \ "$API_URL/devices") - checkCurlOutput "$curlres" + curlretcode=$? + set -e + checkCurlOutput "$curlres" "$curlretcode" info "Device has been created" ;; list) @@ -270,16 +337,22 @@ chat) case $2 in create) # create a chat with the email in $3 so that the address shows up in the list command - if [[ ! "$3" == *@* ]]; then - err "$3 is no email address" - exit 1 - fi + case "$2" in + *@*) + err "'$3' is no email address" + exit ${ERR_FMT} + ;; + esac # as long as the object is an email the chat will either be created or already exists - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --header "Content-Type: application/json" \ --data-binary \{\"email\":\"$3\"\} \ --request POST \ "$API_URL/chats") + curlretcode=$? + set -e + checkCurlReturnCode "$curlretcode" ;; *) printUsage @@ -328,10 +401,13 @@ delete) ;; all) info "deleting all pushes" - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --request DELETE \ "$API_URL/pushes") - checkCurlOutput "$curlres" + curlretcode=$? + set -e + checkCurlOutput "$curlres" "$curlretcode" ;; except) # test if $3 is not empty and a number @@ -340,20 +416,24 @@ delete) fi info "deleting all pushes except the last $3" number=$(($3+1)) - while read line; do - iden=${line%%:*} - # call pushbullet-bash itself to delete the pushes - # use tr to remove quotes around iden - $0 delete $(echo "$iden" | tr -d '"') - # pushes are always displayed with the newest first. By tailing the output we skip the newest $number - done < <(getPushes | getactivepushes | tail -n "+$number") + getPushes | getactivepushes | tail -n "+$number" | \ + while read line; do + iden=${line%%:*} + # call pushbullet-bash itself to delete the pushes + # use tr to remove quotes around iden + $0 delete $(echo "$iden" | tr -d '"') + # pushes are always displayed with the newest first. By tailing the output we skip the newest $number + done ;; *) info "deleting $2" - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --request DELETE \ "$API_URL/pushes/$2") - checkCurlOutput "$curlres" + curlretcode=$? + set -e + checkCurlOutput "$curlres" "$curlretcode" ;; esac ;; @@ -390,10 +470,10 @@ push) body=$(echo "$body"|tr -dc '[:print:]\n'|tr '"' "'") fi - if [[ $4 == http://* ]] || [[ $4 == https://* ]]; then + if expr "$4" : 'https\?://*' > /dev/null ; then body=${body:-$5} url="$4" - elif [[ $5 == http://* ]] || [[ $5 == https://* ]]; then + elif expr "$5" : 'https\?://*' > /dev/null ; then body=${body:-$6} url="$5" else @@ -402,7 +482,7 @@ push) fi # replace newlines with an escape sequence - body="${body//$'\n'/\\n}" + body=$(printf '%s\n' "$body" | sed 's/\n/\\n/g') case $3 in note) @@ -411,34 +491,40 @@ push) ;; link) type=link - if [[ ! $url == http://* ]] && [[ ! $url == https://* ]]; then + if expr "$url" : 'https\?://*' > /dev/null ; then err "Error: A valid link has to start with http:// or https://" - exit 1 + exit ${ERR_FMT} fi json="{\"type\":\"$type\",\"title\":\"$title\",\"body\":\"$body\",\"url\":\"$url\"" ;; file) file=$4 - if [[ -d $file ]]; then + if [ -d $file ]; then info "Given file is actually a folder, compressing it first" archivename="$(date +%s)" tar cfz /tmp/"$archivename.tar.gz" "$file" file=/tmp/"$archivename.tar.gz" fi - if [[ -z $file ]] || [[ ! -f $file ]]; then + if [ -z $file ] || [ ! -f $file ]; then err "Error: no valid file to push was specified" - exit 1 + exit ${ERR_NOFILE} fi # Api docs: https://docs.pushbullet.com/v2/upload-request/ mimetype=$(file -i -b "$file") - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --header "Content-Type: application/json" \ --data-binary "{\"file_name\":\"$file\",\"file_type\":\"${mimetype%:*}\"}" \ --request POST \ "$API_URL/upload-request") - curlres2=$(curl --silent --include --request POST \ + curlretcode=$? + checkCurlReturnCode "$curlretcode" + curlres2=$(curl $CURLOPTS --include --request POST \ $(echo "$curlres" | "$PROGDIR"/JSON.sh -b | grep upload_url | awk -F\" '{print $(NF-1)}') \ -F file=@"$file") + curlretcode=$? + set -e + checkCurlReturnCode "$curlretcode" type=file file_name=$(echo "$curlres" | "$PROGDIR"/JSON.sh -b | grep file_name |awk -F\" '{print $(NF-1)}') @@ -455,7 +541,7 @@ push) info "Sending to All Devices" json="$json}" # $2 must be a contact/an email address if it contains an @. - elif [[ "$2" == *@* ]]; then + elif expr "$2" : '*@*' > /dev/null ; then info "Sending to email address $2" json="$json,\"email\":\"$2\"}" # since it's an email we are also creating a chat @@ -469,12 +555,15 @@ push) info "Sending to device $dev_name" json="$json,\"device_iden\":\"$dev_id\"}" fi - curlres=$(curl --silent --header "Access-Token: $PB_API_KEY" \ + set +e + curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \ --header "Content-type: application/json" \ --data-binary "$json" \ --request POST \ "$API_URL/pushes") - checkCurlOutput "$curlres" + curlretcode=$? + set -e + checkCurlOutput "$curlres" "$curlretcode" ;; setup) CLIENT_ID=RP56dyRen86HaaLnXBevnrDTHT8fTcr6 @@ -487,10 +576,10 @@ setup) info info "Before continuing you need to save your newly created token in $PB_CONFIG" - if [ "$(uname)" == "Darwin" ]; then + if expr "$(uname)" : "Darwin" > /dev/null ; then open "$OAUTH_URL" else - xdg-open "$OAUTH_URL" &> /dev/null + xdg-open "$OAUTH_URL" > /dev/null 2>&1 fi ;; ratelimit)