#!/usr/bin/env bash # discord colors are standard 6-digit hex, but converted to a base-10 integer # colors taken from https://xkcd.com/color/rgb/ # and built into a palette based on https://sashamaps.net/docs/resources/20-colors/ # into my color palette https://color.digitaladapt.com/ # # DARK: maroon(#650021), brown(#653700), olive(#6e750e), teal(#029386), navy(#01153e), black(#000000), # BRIGHT: red(#e50000), orange(#f97306), yellow(#ffff14), lime(#aaff32), green(#15b01a), # cyan(#00ffff), blue(#0343df), purple(#7e1e9c), magenta(#c20078), grey(#929591), # LIGHT: pink(#ff81c0), apricot(#ffb16d), beige(#e6daa6), mint(#9ffeb0), lavender(#c79fef), white(#ffffff). scriptRoot=$(dirname "$0") alert=false quiet=false skipMsg=false declare -A colors colors=( ["maroon"]=6619169 ["brown"]=6633216 ["olive"]=7238926 ["teal"]=168838 ["navy"]=70974 ["black"]=0 ["red"]=15007744 ["orange"]=16347910 ["yellow"]=16776980 ["lime"]=11206450 ["green"]=1421338 ["cyan"]=65535 ["blue"]=213983 ["purple"]=8265372 ["magenta"]=12714104 ["grey"]=9606545 ["pink"]=16744896 ["apricot"]=16757101 ["beige"]=15129254 ["mint"]=10485424 ["lavender"]=13082607 ["white"]=16777215 ) declare -A ansiColors ansiColors=( ["maroon"]="\e[37m\e[48;2;101;0;33m" ["brown"]="\e[37m\e[48;2;101;55;0m" ["olive"]="\e[37m\e[48;2;110;117;14m" ["teal"]="\e[37m\e[48;2;2;147;134m" ["navy"]="\e[37m\e[48;2;1;21;62m" ["black"]="\e[37m\e[48;2;0;0;0m" ["red"]="\e[30m\e[48;2;229;0;0m" ["orange"]="\e[30m\e[48;2;249;115;6m" ["yellow"]="\e[30m\e[48;2;255;255;20m" ["lime"]="\e[30m\e[48;2;170;255;50m" ["green"]="\e[30m\e[48;2;21;176;26m" ["cyan"]="\e[30m\e[48;2;0;255;255m" ["blue"]="\e[30m\e[48;2;3;67;223m" ["purple"]="\e[30m\e[48;2;126;30;156m" ["magenta"]="\e[30m\e[48;2;194;0;120m" ["grey"]="\e[30m\e[48;2;146;149;145m" ["pink"]="\e[30m\e[48;2;255;129;192m" ["apricot"]="\e[30m\e[48;2;255;177;109m" ["beige"]="\e[30m\e[48;2;230;218;166m" ["mint"]="\e[30m\e[48;2;159;254;176m" ["lavender"]="\e[30m\e[48;2;199;159;239m" ["white"]="\e[30m\e[48;2;255;255;255m" ) print_usage() { echo "Usage: $0 [[-h hook-url] | -a] [-c color] [-d [distinct-name]] [-t title] [-q] (message... | -z | < file.msg)" } # handle all arguments provided while getopts ':h:c:d:t:zaq' flag; do case "$flag" in h) if [[ "$OPTARG" == "http"*"?wait=true" ]]; then hookUrl="$OPTARG" else echo "Invalid hook-url provided, should be something like:" echo "https://discord.com/api/webhooks/***?wait=true" print_usage exit 1 fi ;; c) if [[ -v "colors[$OPTARG]" ]]; then color="$OPTARG" else echo "Invalid color provided" "${scriptRoot}/colors-discord.sh" print_usage exit 1 fi ;; d) # whatever the distinct key, we clean it up, since it will be used as a filename # replace "/" and "|" with "_" distinct=$(echo "$OPTARG" | sed 's/[\/\|]/_/g') ;; :) # '-d' without value is allowed, but generates a warning # in which case we'll use name of parent command with arguments # whatever the distinct key, we clean it up, since it will be used as a filename # replace "/" and "|" with "_" if [[ "$OPTARG" = "d" ]]; then distinct=$(ps -o args= $PPID | sed 's/[\/\|]/_/g') echo "Warning: doing distinct by parent, with key '$distinct'" >&2 else echo "option '$OPTARG' provided with value" print_usage exit 1 fi ;; t) # title, for supplying a title, without activing the "distinct" feature title="$OPTARG" ;; z) skipMsg=true ;; q) quiet=true ;; a) alert=true ;; *) echo "unknown option provided '$flag'" print_usage exit 1 ;; esac done shift "$((OPTIND-1))" # load in defaults from config configFile="$scriptRoot/config.sh" if [[ -f "$configFile" ]]; then source "$configFile" botName="$DISCORD_SERVER_NAME" if [[ -z "$hookUrl" ]]; then hookUrl="$DISCORD_GENERAL_HOOK" if [[ "$alert" = true ]]; then hookUrl="$DISCORD_ALERT_HOOK" fi fi if [[ -n "$title" ]]; then title="$title $DISCORD_TITLE_SUFFIX" fi fi # stop if we have no hook to send message to if [[ -z "$hookUrl" ]]; then echo "Discord Hook URL must either be in config file, or supplied via '-h' argument." exit 1 fi # get full message, read from arguments message=$(echo "$@" | sed 's/^ *//' | sed 's/ *$//') # if we have no message, read from stdin instead if [[ -z "$message" ]] && [[ "$skipMsg" = false ]]; then message=$(cat - | sed 's/^ *//' | sed 's/ *$//') fi # sed removes blank lines from the end of the message message=$(echo "$message" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}') fullMsg="$message" # output the given message, unless instructed to be quiet if [[ "$quiet" = false ]]; then if [[ -n "$distinct" ]] || [[ -n "$title" ]] || [[ -n "$color" ]]; then if [[ -n "$color" ]]; then echo -e "${ansiColors[$color]} ${title-$distinct} \e[m" else # no color specified, make title bold and underlined echo -e "\e[1m\e[4m ${title-$distinct} \e[m" fi fi if [[ -n "$fullMsg" ]] && [[ "$skipMsg" = false ]]; then echo "$fullMsg" fi fi # if message is too long, split it, and after curl, recursively call with remainder # 4k max, but we must account for escaping text in json, so limit to 90% capacity extra="${message:3600}" if [ -n "$extra" ]; then message="${message:0:3600}" # as an added elegance, when spliting into pieces, find the last \n newline, # and break there instead, but only if the new smaller chunk is of sufficient size chunk1=$(echo "$message" | head -n -1) chunks=$(echo "$message" | tail -n 1) if (( "${#chunk1}" >= 1000 )); then message="$chunk1" extra="$chunks$extra" fi fi # sed: rewrite json string, because "\u001b[m" needs to "\u001b[0m" instead # linux is fine with the missing zero, but discord needs it message=$(echo "$message" | jq -aRs . | sed 's/\\u001b\[m/\\u001b[0m/g') if [[ "$message" = '"\n"' ]]; then if [[ "$quiet" = true ]]; then # quiet was specified, and there was no message, so stop now exit 0 else # message empty, so denote as such message='"\ud83d\udea9 MESSAGE EMPTY \ud83d\udea9"' fi else if [[ "$message" = *"\\u001b"* ]]; then prefix="ansi\n" fi # wrap message into "ansi" block, for text color support message="\"\`\`\`$prefix${message:1:-1}\`\`\`\"" fi # handle distinct stuff oldMsg="" if [[ -n "$distinct" ]]; then if [[ -f "$scriptRoot/unique/$distinct.msg" ]]; then oldMsg=$(cat "$scriptRoot/unique/$distinct.msg") fi if [[ "$fullMsg" = "$oldMsg" ]]; then echo "duplicate message" exit 0 fi echo "$fullMsg" > "$scriptRoot/unique/$distinct.msg" fi # build content json content="{" if [[ -n "$botName" ]]; then content="$content\"username\": $(echo "$botName" | jq -aRs .), " fi content="$content\"embeds\":[{\"description\": " if [[ "$skipMsg" = false ]]; then content="$content$message" else content="$content\"\"" fi if [[ -n "$color" ]]; then content="$content, \"color\": ${colors[$color]}" fi if [[ -n "$distinct" ]] || [[ -n "$title" ]]; then content="$content, \"title\": $(echo "${title-$distinct}" | jq -aRs .)" fi content="$content}]}" # actually send the message to discord response=$(curl -s -H "Content-Type: application/json" -X POST -d "$content" "$hookUrl" | jq -r '.message') # response message is null unless there was an issue if [[ "$response" != "null" ]]; then echo "$response" >&2 fi # if the message was too long, send along the rest as a separate message if [ -n "$extra" ]; then script=$(basename "$0") args=("-h" "$hookUrl") # when we call again, we convert distinct into a title if [[ -n "$distinct" ]] || [[ -n "$title" ]]; then args+=("-t" "${title-$distinct}") fi if [[ -n "$color" ]]; then args+=("-c" "$color") fi "$scriptRoot/$script" ${args[@]} "$extra" fi