-
Notifications
You must be signed in to change notification settings - Fork 41
/
pushbullet
executable file
·598 lines (549 loc) · 15.8 KB
/
pushbullet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
#! /bin/sh
# Bash interface to the PushBullet api.
# Author: Red5d - https://github.com/Red5d
API_URL=https://api.pushbullet.com/v2
PROGDIR="$(cd "$(dirname "$(readlink -f "$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
echo "$@"
fi
}
err() {
if [ -w /dev/stderr ]; then
echo "$@" > /dev/stderr
else
# /dev/stderr does not exist or is not writable
echo "$@"
fi
}
if [ ! "$(which curl)" ]; then
err "pushbullet-bash requires curl to run. Please install curl."
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 ${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
PB_CONFIG=~/.config/pushbullet
fi
. $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
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 ${ERR_CFG}
fi
# from here on set -e to avoid "EXPECTED value GOT EOF" errors from JSON.sh when no pushes are returned
set -e
printUsage() {
echo "Usage: pushbullet [-q|--quiet|-d|--debug] <action> <device> <type> <data>
Options:
-q, --quiet - only print error messages.
-d, --debug - print debug output
Actions:
list - List all devices and contacts in your PushBullet account. (does not require
additional parameters)
push - Push data to a device or contact. (the device name can simply be
a unique part of the name that \"list\" returns)
pushes active - List your 'active' pushes (pushes that haven't been deleted).
Format is: iden:type:title:modified
pushes recent - List all of your 'active' pushes since the last run.
pushes last24h - List 'active' pushes from the last 24 hours.
delete \$iden - Delete a specific push.
delete except \$number - Delete all pushes except the last \$number.
delete all - Delete all pushes.
pull \$iden - Display a specific push.
setup - Use OAuth to retrieve a PushBullet API key for pushbullet-bash.
create-device - create a device in pushbullet named like the current hostname.
chat create \$email - create a chat with the passed email. Does not send a message.
Types:
note
link
file
Type Parameters:
(all parameters must be put inside quotes if more than one word)
\"note\" type: give the title and an optional message body.
\"link\" type: give an optional title, an optional message and the url. The optional message can also be given last.
\"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 ${ERR_NONE}
}
getactivepushes () {
# first command in a function reads stdin
allpushes=$("$PROGDIR"/JSON.sh -b)
activepushes=$(echo "$allpushes" | egrep "\"pushes\",[0-9]+,\"active\"\].*true" \
| while read line; do id=${line%,*}; echo $id; done)
for id in $activepushes; do
# use grep -F to not interpred ] (which is also stored in $id)
iden=$(echo "$allpushes" | grep -F "$id,\"iden\"]" |cut -f 2)
title=$(echo "$allpushes" | grep -F "$id,\"title\"]"|cut -f 2)
modified=$(echo "$allpushes" | grep -F "$id,\"modified\"]" |cut -f 2)
type=$(echo "$allpushes" | grep -F "$id,\"type\"]" |cut -f 2)
# set title if none is stored
title=${title:-"(no title)"}
echo "$iden:$type:$title:$modified"
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)
curlretcode=${2:-$ERR_UNK}
checkCurlReturnCode "$curlretcode"
case "$1" in
*"The param 'channel_tag' has an invalid value."* | *"The param 'device_iden' has an invalid value."*)
err "Error: You specified an unknown device or channel."
exit ${ERR_PB_DEVICE}
;;
*"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
checkPagination() {
cursor=$(echo "$1" | tr ',' '\n' | grep \"cursor\": | cut -d '"' -f4)
echo "$cursor"
}
getChats() {
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
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
echo "$chats"
}
getDevices() {
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" "$curlretcode"
# check if query needs pagination
devices=$curlres
cursor=$(checkPagination "$curlres")
until [ -z $cursor ]; do
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
echo "$devices"
}
getPushes() {
# function gets a list of pushes (as a json object)
local OPTIND
while getopts ":a:i:m:" OPTION; do
case "$OPTION" in
a)
active="$OPTARG"
;;
i)
iden="$OPTARG"
;;
m)
modified="$OPTARG"
;;
esac
done
shift $((OPTIND-1))
# default to true to only get active pushes if not set otherwise
active=${active:-true}
# default to 0 to get all pushes if not set otherwise
modified=${modified:-0}
# if $iden is set, add leading slash
if [ ! -z "$iden" ]; then
iden="/$iden"
fi
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")
curlretcode=$?
set -e
checkCurlOutput "$curlres" "$curlretcode"
response="[ $curlres"
# check if query needs pagination
until [ -z "$(checkPagination "$curlres")" ]; do
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" "$curlretcode"
done
echo "$response ]"
}
case $1 in
-q|--quiet)
QUIET=1
shift
;;
-d|--debug)
set -x
shift
;;
esac
case $1 in
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 ${ERR_PB_DEVICE}
fi
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")
curlretcode=$?
set -e
checkCurlOutput "$curlres" "$curlretcode"
info "Device has been created"
;;
list)
info "Available devices:"
info "------------------"
devices=$(getDevices)
echo "$devices" | tr ',' '\n' | grep \"nickname\": | sort -n | cut -d '"' -f4
info "all"
info
info "Chats/Contacts:"
info "------------------"
chats=$(getChats)
echo "$chats" | tr ',' '\n' | grep \"email\": | cut -d '"' -f4 | sort -n
;;
chat)
case $2 in
create)
# create a chat with the email in $3 so that the address shows up in the list command
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
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
;;
esac
;;
pushes)
case $2 in
active)
info "Your active pushes:"
info "------------------"
getPushes | getactivepushes
;;
recent)
# set LASTMODIFIED (unix time) to zero if empty
if [ -z "$PB_LASTMODIFIED" ]; then
PB_LASTMODIFIED=0
echo "PB_LASTMODIFIED=0" >> $PB_CONFIG
fi
info "Your recent pushes:"
info "------------------"
pushes=$(getPushes -m $PB_LASTMODIFIED | getactivepushes)
echo "$pushes"
# get the modififed timestamp of the newest push
pushes=$(echo "$pushes" | head -n 1)
modified=${pushes##*:}
# only update PB_LASTMODIFIED if $modified has a value
if [ ! -z $modified ]; then
sed --follow-symlinks -i "s/^PB_LASTMODIFIED=.*/PB_LASTMODIFIED=$modified/" $PB_CONFIG
fi
;;
last24h)
info "Your pushes from the last 24 hours:"
info "------------------"
getPushes -m $(date -d "1 day ago" +%s) | getactivepushes
;;
*)
printUsage
;;
esac
;;
delete)
case $2 in
"")
printUsage
;;
all)
info "deleting all pushes"
set +e
curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \
--request DELETE \
"$API_URL/pushes")
curlretcode=$?
set -e
checkCurlOutput "$curlres" "$curlretcode"
;;
except)
# test if $3 is not empty and a number
if [ -z "${3##*[!0-9]}" ]; then
printUsage
fi
info "deleting all pushes except the last $3"
number=$(($3+1))
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"
set +e
curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \
--request DELETE \
"$API_URL/pushes/$2")
curlretcode=$?
set -e
checkCurlOutput "$curlres" "$curlretcode"
;;
esac
;;
pull)
case $2 in
"")
printUsage
;;
*)
getPushes -i $2 | "$PROGDIR"/JSON.sh -b
;;
esac
;;
push)
if [ -z "$2" ]; then
printUsage
fi
# get device listing
curlres=$(getDevices)
devices=$(echo "$curlres" | tr '{' '\n' | tr ',' '\n' | grep \"nickname\" | cut -d'"' -f4)
idens=$(echo "$curlres" | tr '{' '\n' | grep active\"\:true | tr ',' '\n' | grep iden | cut -d'"' -f4)
lineNum=$(echo "$devices" | grep -i -n "$2" | cut -d: -f1)
# check the lenght of $lineNum, if it returns more than one entry then the search was not unique enough
lineLength=$(echo $lineNum | wc -w)
if [ $lineLength -gt '1' ]; then
err "The given device name is not unique enough. Please check 'pushbullet list'"
exit ${ERR_PB_DEVICE}
fi
dev_id=$(echo "$idens" | sed -n $lineNum'p')
dev_name=$(echo "$devices" | sed -n $lineNum'p')
title="$4"
body=""
if [ ! -t 0 ]; then
# we have something on stdin
body=$(cat)
# remove unprintable characters, or pushbullet API fails
body=$(echo "$body"|tr -dc '[:print:]\n'|tr '"' "'")
fi
if expr "$4" : 'https\?://*' > /dev/null ; then
body=${body:-$5}
url="$4"
elif expr "$5" : 'https\?://*' > /dev/null ; then
body=${body:-$6}
url="$5"
else
body=${body:-$5}
url="$6"
fi
# replace newlines with an escape sequence
body=$(printf '%s\n' "$body" | sed 's/\n/\\n/g')
case $3 in
note)
type=note
json="{\"type\":\"$type\",\"title\":\"$title\",\"body\":\"$body\""
;;
link)
type=link
if ! expr "$url" : 'https\?://*' > /dev/null ; then
err "Error: A valid link has to start with http:// or https://"
exit ${ERR_FMT}
fi
json="{\"type\":\"$type\",\"title\":\"$title\",\"body\":\"$body\",\"url\":\"$url\""
;;
file)
file=$4
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
err "Error: no valid file to push was specified"
exit ${ERR_NOFILE}
fi
# Api docs: https://docs.pushbullet.com/v2/upload-request/
mimetype=$(file -i -b "$file")
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")
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)}')
file_type=$(echo "$curlres" | "$PROGDIR"/JSON.sh -b | grep file_type |awk -F\" '{print $(NF-1)}')
file_url=$(echo "$curlres" | "$PROGDIR"/JSON.sh -b | grep file_url |awk -F\" '{print $(NF-1)}')
json="{\"type\":\"$type\",\"title\":\"$title\",\"body\":\"$body\",\"file_name\":\"$file_name\",\"file_type\":\"$file_type\",\"file_url\":\"$file_url\""
;;
*)
printUsage
;;
esac
if [ "$2" = "all" ]; then
info "Sending to All Devices"
json="$json}"
# $2 must be a contact/an email address if it contains an @.
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
$0 chat create "$2"
# $2 must be a channel_tag if $lineNum is empty.
elif [ -z "$lineNum" ]; then
info "Sending to channel $2"
json="$json,\"channel_tag\":\"$2\"}"
# in all other cases $2 must be the identifier of a device.
else
info "Sending to device $dev_name"
json="$json,\"device_iden\":\"$dev_id\"}"
fi
set +e
curlres=$(curl $CURLOPTS --header "Access-Token: $PB_API_KEY" \
--header "Content-type: application/json" \
--data-binary "$json" \
--request POST \
"$API_URL/pushes")
curlretcode=$?
set -e
checkCurlOutput "$curlres" "$curlretcode"
;;
setup)
CLIENT_ID=RP56dyRen86HaaLnXBevnrDTHT8fTcr6
REDIRECT_URI="https://Red5d.github.io/pushbullet-bash"
OAUTH_URL="https://www.pushbullet.com/authorize?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=token&scope=everything"
info
info "Please open the following URL manually if it did not open automatically:"
info
echo "$OAUTH_URL"
info
info "Before continuing you need to save your newly created token in $PB_CONFIG"
if expr "$(uname)" : "Darwin" > /dev/null ; then
open "$OAUTH_URL"
else
xdg-open "$OAUTH_URL" > /dev/null 2>&1
fi
;;
ratelimit)
curl --header "Access-Token: $PB_API_KEY" \
--include \
"$API_URL/users/me"
echo
;;
*)
printUsage
;;
esac