Skip to content

Commit 32ec005

Browse files
committedJun 12, 2020
Offsite backup will now send notifications to a Discord channel while it is in progress. Added ChaoticWeg's discord.sh, and changed the software license of these scripts from MIT to GNUv3 to conform with discord.sh's license terms.
1 parent 12f7549 commit 32ec005

7 files changed

+1106
-26
lines changed
 

‎LICENSE

+674-21
Large diffs are not rendered by default.

‎README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Internal bash scripts used by the **[Minecart Rapid Transit (MRT) Minecraft Serv
55

66
**Please note that the code here is provided as-is and no further documentation will be provided.**
77

8+
## Dependencies
9+
For notifications to Discord, these scripts use [ChaoticWeg's discord.sh](https://github.com/ChaoticWeg/discord.sh).
10+
811
## License
9-
These scripts are licensed under the [MIT License](https://choosealicense.com/licenses/mit/).
12+
These scripts are licensed under the [GNU General Public License v3.0](https://choosealicense.com/licenses/gpl-3.0/).
1013

‎backup_minecraft_to_ftp.sh

+6-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ backup_minecraft_to_ftp()
3131
"-f" "[Server] Offsite backup cleanup complete" \
3232
"-m" "60" \
3333
"-c" "light_purple" \
34-
"-o" "bold,italic"
34+
"-o" "bold,italic" \
35+
"-d" "true" \
36+
"-u" "Offsite Backup"
3537

3638
run_progress_timer "run_ftp_backup" \
3739
"-s" "[Server] Starting offsite backup..." \
@@ -40,7 +42,9 @@ backup_minecraft_to_ftp()
4042
"-m" "60" \
4143
"-h" "true" \
4244
"-c" "light_purple" \
43-
"-o" "bold,italic"
45+
"-o" "bold,italic" \
46+
"-d" "true" \
47+
"-u" "Offsite Backup"
4448

4549
unset FTP_PASSWORD
4650
}

‎config/discord_config

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
DISCORD_WEBHOOK=

‎lib/discord_utils.sh

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
source $SCRIPT_DIR/config/discord_config
3+
4+
send_message_to_discord() {
5+
if [[ -z $2 ]]; then
6+
$SCRIPT_DIR/lib/vendor/discord.sh --webhook-url="$DISCORD_WEBHOOK" --text "$1"
7+
else
8+
$SCRIPT_DIR/lib/vendor/discord.sh --webhook-url="$DISCORD_WEBHOOK" --text "$1" --username "$2"
9+
fi
10+
}

‎lib/progress_timer.sh

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/bin/bash
2+
source $SCRIPT_DIR/lib/discord_utils.sh
23
source $SCRIPT_DIR/lib/minecraft_server_control.sh
34
source $SCRIPT_DIR/lib/time_utils.sh
45

@@ -9,6 +10,10 @@ timer_text()
910
else
1011
tellraw_in_minecraft "$1" "$tellraw_color" "$tellraw_options"
1112
fi
13+
14+
if [[ ! -z $send_to_discord ]]; then
15+
send_message_to_discord "$1" "$discord_username"
16+
fi
1217
}
1318

1419
run_progress_timer()
@@ -25,7 +30,7 @@ run_progress_timer()
2530
local message_interval_in_seconds=1
2631
local show_hours=false
2732

28-
while getopts "s:p:f:m:h:c:o:" OPTION
33+
while getopts "s:p:f:m:h:c:o:d:u:" OPTION
2934
do
3035
case $OPTION in
3136
s) start_message="$OPTARG"
@@ -42,7 +47,11 @@ run_progress_timer()
4247
;;
4348
o) tellraw_options="$OPTARG"
4449
;;
45-
?) printf "Usage: %s <task_function> [-s <start_message>] [-p <progress_message>] [-f <finish_message>] [-m <message_interval>] [-h <show_hours>] [-c <tellraw_color>] [-o <tellraw_options>]" $(basename $0) >&2
50+
d) send_to_discord=$OPTARG
51+
;;
52+
u) discord_username="$OPTARG"
53+
;;
54+
?) printf "Usage: %s <task_function> [-s <start_message>] [-p <progress_message>] [-f <finish_message>] [-m <message_interval>] [-h <show_hours>] [-c <tellraw_color>] [-o <tellraw_options>] [-d <send_to_discord>] [-u <discord_username>]" $(basename $0) >&2
4655
exit 2
4756
;;
4857
esac

‎lib/vendor/discord.sh

+398
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Discord.sh - Discord on command-line
4+
# by ChaoticWeg and Suce
5+
6+
shopt -s lastpipe # avoid subshell weirdness hopefully
7+
shopt -so pipefail # hopefully correctly get $? in substitution
8+
9+
# check for jq
10+
11+
jq --version >/dev/null 2>&1
12+
jq_ok=$?
13+
14+
[[ "$jq_ok" -eq 127 ]] && \
15+
echo "fatal: jq not installed" && exit 2
16+
[[ "$jq_ok" -ne 0 ]] && \
17+
echo "fatal: unknown error in jq" && exit 2
18+
19+
# jq exists and runs ok
20+
21+
# check for curl
22+
23+
curl --version >/dev/null 2>&1
24+
curl_ok=$?
25+
26+
[[ "$curl_ok" -eq 127 ]] && \
27+
echo "fatal: curl not installed" && exit 2
28+
29+
# curl exists and runs ok
30+
31+
get_ts() { date -u --iso-8601=seconds; };
32+
33+
thisdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
34+
webhook_file="${thisdir}/.webhook"
35+
36+
help_text="Usage: discord.sh --webhook-url=<url> [OPTIONS]
37+
38+
General options:
39+
--help Display this help and exit
40+
--text <text> Body text of message to send
41+
--tts Send message with text-to-speech enabled
42+
--webhook-url Specify the Discord webhook URL
43+
44+
Identity options:
45+
--username <name> Set username to <name>
46+
--avatar <url> Set avatar to image located at <url>
47+
48+
Embedded content options:
49+
Main:
50+
--title <title> Display embed title as <title>
51+
--description <description> Display embed description as <description>
52+
--url <url> URL of content
53+
--color <color> Set color of bar on left border of embed
54+
Syntax of <color>:
55+
Option 1: 0x<hexadecimal number> (Example: --color 0xFFFFF)
56+
Option 2: <decimal number> (Example: --color 16777215)
57+
--thumbnail <url> Set thumbnail to image located at <url>
58+
59+
Author:
60+
--author <name> Display author name as <name>
61+
--author-icon <url> Display author icon as image located at <url>
62+
--author-url <url> Set author title to go to <url> when clicked
63+
64+
Image:
65+
--image <url> Set image to image located at <url>
66+
--image-height <number> Set image height to <number> pixels
67+
--image-width <number> Set image width to <number> pixels
68+
69+
Footer:
70+
--footer <text> Display <text> in footer
71+
--footer-icon <url> Display image located at <url> in footer
72+
--timestamp Display timestamp"
73+
74+
# HELP TEXT PLEASE
75+
[[ "$#" -eq 0 ]] && echo "$help_text" && exit 0
76+
[[ "${1}" == "help" ]] && echo "$help_text" && exit 0
77+
78+
# gather arguments
79+
while (( "$#" )); do
80+
case "${1}" in
81+
--help) echo "$help_text" && exit 0;;
82+
-h) echo "$help_text" && exit 0;;
83+
84+
--dry-run) is_dry=1; shift;;
85+
--tts) is_tts=1; shift;;
86+
87+
--webhook-url=*) webhook_url=${1/--webhook-url=/''}; shift;;
88+
--webhook-url*) webhook_url=${2}; shift; shift;;
89+
90+
--username=*) username=${1/--username=/''}; shift;;
91+
--username*) username=${2}; shift; shift;;
92+
93+
--avatar=*) avatar_url=${1/--avatar=/''}; shift;;
94+
--avatar*) avatar_url=${2}; shift; shift;;
95+
96+
--text=*) text=${1/--text=/''}; shift;;
97+
--text*) text=${2}; shift; shift;;
98+
99+
# embed goodies
100+
101+
--title=*) embed_title=${1/--title=/''}; embedding=1; shift;;
102+
--title*) embed_title=${2}; embedding=1; shift; shift;;
103+
104+
--description=*) embed_description=${1/--description=/''}; embedding=1; shift;;
105+
--description*) embed_description=${2}; embedding=1; shift; shift;;
106+
107+
--url=*) embed_url=${1/--url=/''}; embedding=1; shift;;
108+
--url*) embed_url=${2}; embedding=1; shift; shift;;
109+
110+
--color=*) embed_color=${1/--color=/''}; embedding=1; shift;;
111+
--color*) embed_color=${2}; embedding=1; shift; shift;;
112+
113+
--timestamp*) embed_timestamp=1; shift;;
114+
115+
# embed author
116+
117+
--author-url=*) embed_authorurl=${1/--author-url=/''}; embedding=1; shift;;
118+
--author-url*) embed_authorurl=${2}; embedding=1; shift; shift;;
119+
120+
--author-icon=*) embed_authoricon=${1/--author-icon=/''}; embedding=1; shift;;
121+
--author-icon*) embed_authoricon=${2}; embedding=1; shift; shift;;
122+
123+
--author=*) embed_authorname=${1/--author=/''}; embedding=1; shift;;
124+
--author*) embed_authorname=${2}; embedding=1; shift; shift;;
125+
126+
# thumbnail
127+
128+
--thumbnail=*) embed_thumbnail=${1/--thumbnail=/''}; embedding=1; shift;;
129+
--thumbnail*) embed_thumbnail=${2}; embedding=1; shift; shift;;
130+
131+
# image
132+
133+
--image-height=*) embed_imageheight=${1/--image-height=/''}; embedding=1; shift;;
134+
--image-height*) embed_imageheight=${2}; embedding=1; shift; shift;;
135+
136+
--image-width=*) embed_imagewidth=${1/--image-width=/''}; embedding=1; shift;;
137+
--image-width*) embed_imagewidth=${2}; embedding=1; shift; shift;;
138+
139+
--image=*) embed_imageurl=${1/--image=/''}; embedding=1; shift;;
140+
--image*) embed_imageurl=${2}; embedding=1; shift; shift;;
141+
142+
# footer
143+
144+
--footer-icon=*) embed_footericon=${1/--footer-icon=/''}; embedding=1; shift;;
145+
--footer-icon*) embed_footericon=${2}; embedding=1; shift; shift;;
146+
147+
--footer=*) embed_footertext=${1/--footer=/''}; embedding=1; shift;;
148+
--footer*) embed_footertext=${2}; embedding=1; shift; shift;;
149+
150+
# file
151+
--file=*) file_path=${1/--file=/''}; has_file=1; shift;;
152+
--file*) file_path=${2}; has_file=1; shift; shift;;
153+
154+
# unknown argument. bail out
155+
156+
*) echo "fatal: unknown argument '${1}'"; exit 1;;
157+
158+
esac
159+
done
160+
161+
# files must be standalone
162+
[[ -n "${embedding}" ]] && [[ -n "${has_file}" ]] && \
163+
echo "fatal: files must be sent on their own (i.e. without text or embeds)" && \
164+
exit 3
165+
166+
# set webhook url (if none exists after argument handling)
167+
[[ -z ${webhook_url} ]] && [[ -n "${DISCORD_WEBHOOK}" ]] && webhook_url=${DISCORD_WEBHOOK}
168+
[[ -z ${webhook_url} ]] && [[ -r "${webhook_file}" ]] && [[ -f "${webhook_file}" ]] && webhook_url=$(cat "${webhook_file}")
169+
170+
# no webhook could be found. bail out
171+
[[ -z ${webhook_url} ]] && echo "fatal: no --webhook-url passed or no .webhook file to read from" && exit 1;
172+
173+
174+
enforce_limits() {
175+
# title <= 256
176+
[[ -n "${embed_title}" ]] && [[ "${#embed_title}" -gt 256 ]] && \
177+
embed_title="${embed_title::256}" && \
178+
echo "warning: embed title limited to ${#embed_title} characters"
179+
180+
# description <= 2048
181+
[[ -n "${embed_description}" ]] && [[ "${#embed_description}" -gt 2048 ]] && \
182+
embed_description="${embed_description::2048}" && \
183+
echo "warning: embed description limited to ${#embed_description} characters"
184+
185+
# footer.text <= 2048
186+
[[ -n "${embed_footertext}" ]] && [[ "${#embed_footertext}" -gt 2048 ]] && \
187+
embed_footertext="${embed_footertext::2048}" && \
188+
echo "warning: embed footer text limited to ${#embed_footertext} characters"
189+
190+
# author.name <= 256
191+
[[ -n "${embed_authorname}" ]] && [[ "${#embed_authorname}" -gt 256 ]] && \
192+
embed_authorname="${embed_authorname::256}" && \
193+
echo "warning: embed author name limited to ${#embed_authorname} characters"
194+
}
195+
196+
197+
##
198+
# build embed author object
199+
##
200+
build_author() {
201+
# don't build if not embedding
202+
[[ -z "${embedding}" ]] && exit;
203+
[[ "${embedding}" -ne 1 ]] && exit;
204+
205+
[[ -n "${embed_authorname}" ]] && local _name=", \"name\": \"${embed_authorname}\""
206+
[[ -n "${embed_authorurl}" ]] && local _url=", \"url\": \"${embed_authorurl}\""
207+
[[ -n "${embed_authoricon}" ]] && local _icon=", \"icon_url\": \"${embed_authoricon}\""
208+
209+
echo ", \"author\": { \"_\": \"_\"${_name}${_url}${_icon} }"
210+
}
211+
212+
213+
##
214+
# build thumbnail object
215+
##
216+
build_thumbnail() {
217+
# don't build if not embedding
218+
[[ -z "${embedding}" ]] && exit;
219+
[[ "${embedding}" -ne 1 ]] && exit;
220+
221+
[[ -n "${embed_thumbnail}" ]] && local _url="\"url\": \"${embed_thumbnail}\""
222+
223+
echo ", \"thumbnail\": { ${_url} }"
224+
}
225+
226+
227+
##
228+
# build footer object
229+
##
230+
build_footer() {
231+
# don't build if not embedding
232+
[[ -z "${embedding}" ]] && exit;
233+
[[ "${embedding}" -ne 1 ]] && exit;
234+
235+
[[ -n "${embed_footertext}" ]] && local _text=", \"text\": \"${embed_footertext}\""
236+
[[ -n "${embed_footericon}" ]] && local _icon=", \"icon_url\": \"${embed_footericon}\""
237+
238+
echo ", \"footer\": { \"_\":\"_\"${_text}${_icon} }"
239+
}
240+
241+
242+
##
243+
# build image object
244+
##
245+
build_image() {
246+
# don't build if not embedding
247+
[[ -z "${embedding}" ]] && exit;
248+
[[ "${embedding}" -ne 1 ]] && exit;
249+
250+
[[ -n "${embed_imageurl}" ]] && local _iurl=", \"url\": \"${embed_imageurl}\""
251+
[[ -n "${embed_imageheight}" ]] && local _height=", \"height\": ${embed_imageheight}"
252+
[[ -n "${embed_imagewidth}" ]] && local _width=", \"width\": ${embed_imagewidth}"
253+
254+
echo ", \"image\": { \"_\": \"_\"${_iurl}${_height}${_width} }"
255+
}
256+
257+
##
258+
# build an embed object
259+
##
260+
build_embed() {
261+
# should we embed? if not, bail out without error
262+
[[ -z "${embedding}" ]] && exit;
263+
[[ "${embedding}" -ne 1 ]] && exit;
264+
265+
[[ -n "${embed_title}" ]] && local _title=", \"title\": \"${embed_title}\""
266+
[[ -n "${embed_description}" ]] && local _desc=", \"description\": \"${embed_description}\""
267+
[[ -n "${embed_url}" ]] && local _eurl=", \"url\": \"${embed_url}\""
268+
[[ -n "${embed_color}" ]] && local _color=", \"color\": ${embed_color}"
269+
[[ -n "${embed_timestamp}" ]] && [[ "${embed_timestamp}" -eq 1 ]] && local _ts=", \"timestamp\": \"$(get_ts)\""
270+
271+
local _author="$(build_author)"
272+
local _thumb="$(build_thumbnail)"
273+
local _image="$(build_image)"
274+
local _footer="$(build_footer)"
275+
276+
echo ", \"embeds\": [{ \"_\": \"_\"${_title}${_desc}${_eurl}${_color}${_ts}${_author}${_thumb}${_image}${_footer} }]"
277+
}
278+
279+
280+
build() {
281+
282+
# need to have SOMETHING to build
283+
[[ -z "${has_file}" ]] && \
284+
[[ -z "${text}" ]] && \
285+
[[ -z "${embed_title}" ]] && \
286+
[[ -z "${embed_description}" ]] && \
287+
[[ -z "${embed_imageurl}" ]] && \
288+
echo "fatal: nothing to build" && exit 1
289+
290+
# strip 0x prefix and convert hex to dec if necessary
291+
[[ -n "${embed_color}" ]] && [[ "${embed_color}" =~ ^0x[0-9a-fA-F]+$ ]] && embed_color="$(( embed_color ))"
292+
293+
# embed color must be an integer, if given
294+
[[ -n "${embed_color}" ]] && ! [[ "${embed_color}" =~ ^[0-9]+$ ]] && \
295+
echo "fatal: illegal color '${embed_color}'" && exit 1
296+
297+
# let's build, boys
298+
299+
[[ -n "${is_tts}" ]] && local _tts=", \"tts\": true"
300+
[[ -n "${text}" ]] && local _content=", \"content\": \"${text}\""
301+
[[ -n "${username}" ]] && local _username=", \"username\": \"${username}\""
302+
[[ -n "${avatar_url}" ]] && local _avatar=", \"avatar_url\": \"${avatar_url}\""
303+
[[ -n "${embedding}" ]] && local _embed="$(build_embed)"
304+
305+
local _prefix="\"wait\": true${_tts}${_content}${_username}${_avatar}${_embed}"
306+
307+
echo "{ ${_prefix}${_embed} }"
308+
}
309+
310+
##
311+
# send something to the text channel
312+
##
313+
send()
314+
{
315+
# gotta send something
316+
[[ -z "${1}" ]] && echo "fatal: give me something to send" && exit 1;
317+
318+
local _sendme="${1}"
319+
320+
# dry run?
321+
[[ -n "${is_dry}" ]] && [[ "${is_dry}" -ne 0 ]] && echo "${1}" && exit 0;
322+
323+
# make the POST request and parse the results
324+
# results should be empty if there's no problem. otherwise, there should be code and message
325+
local _result
326+
327+
_result=$(curl -H "Content-Type: application/json" -H "Expect: application/json" -X POST "${webhook_url}" -d "${_sendme}" 2>/dev/null)
328+
send_ok=$?
329+
[[ "${send_ok}" -ne 0 ]] && echo "fatal: curl failed with code ${send_ok}" && exit $send_ok
330+
331+
_result=$(echo "${_result}" | jq '.')
332+
333+
# if we have a result, there was a problem. echo and exit.
334+
[[ -n "${_result}" ]] && \
335+
echo error! "${_result}" && \
336+
echo attempted to send: "$(echo "${_sendme}" | jq '.')" && \
337+
exit 1
338+
339+
exit 0
340+
}
341+
342+
343+
##
344+
# send a file to the channel
345+
##
346+
send_file() {
347+
# gotta have a file
348+
[[ ( -z "${has_file}" ) || ( -z "${file_path}" ) ]] && echo "fatal: give me a file" && exit 4
349+
350+
local _json
351+
if ! _json=$(build); then echo "${_json}"; exit 1; fi
352+
353+
# dry run
354+
if [[ ( -n "${is_dry}" ) && ( "${is_dry}" -ne 0 ) ]]; then
355+
nc -l -N localhost 8000 &
356+
curl -i \
357+
-F "file=@${file_path}" \
358+
-F "${_json}" \
359+
"localhost:8000"
360+
exit 0
361+
fi
362+
363+
[[ -n "${is_dry}" ]] && [[ "${is_dry}" -ne 0 ]] && \
364+
echo "${_json}" && exit 0
365+
366+
# send with correct Content-Type and url-encoded data
367+
curl -i \
368+
-H "Expect: application/json" \
369+
-F "file=@${file_path}" \
370+
-F "payload_json=${_json}" \
371+
"${webhook_url}" >/dev/null 2>&1
372+
373+
# error checking
374+
375+
sent_ok=$?
376+
[[ "${sent_ok}" -eq 0 ]] && exit 0
377+
378+
echo "fatal: curl exited with code ${sent_ok} when sending file \"${file_path}\""
379+
}
380+
381+
382+
## enforce discord API limits
383+
enforce_limits
384+
385+
## no file? build and send normally
386+
if ! [[ "${has_file}" -eq 1 ]]; then
387+
if target=$(build); then
388+
send "${target}"
389+
exit 0
390+
else
391+
echo "${target}"
392+
exit 1
393+
fi
394+
fi
395+
396+
397+
## has file. send as such
398+
send_file

0 commit comments

Comments
 (0)
Please sign in to comment.