#!/usr/bin/env sh # YES_ZSH YES_BASH YES_DASH YES_ASH __notice_warn() { printf 'notice: %s\n' >&2 "$*" } __notice_curl() { curl -q --no-progress-meter 0.0.0.0 >/dev/null 2>&1 if [ $? = 2 ] then __notice_curl() { curl -sS "$@"; } else __notice_curl() { curl --no-progress-meter "$@"; } fi __notice_curl "$@" } __notice_commaize() { # fun with POSIX shell while [ -n "$1" ]; do set -- "${1%???}" "${1#"${1%???}"},$2" "$1" if [ "$1" = "$3" ]; then set -- "" "$1$2" break fi done REPLY="${2%,}" } __notice_escape() { # doesn't work in posh. it doesn't like "${1%%[$2]*}", bug? # warning: mksh is O(n^2) with string length! # note: bash is horribly slow without `export LC_ALL=C`. zsh is fastest. set -- "" "" "$1" "$2" while :; do set -- "$1" "${3%%[$4]*}" "$3" "$4" [ "$2" != "$3" ] || break set -- "$1$2\\" "${3#"$2"}" "$3" "$4" set -- "$1${2%"${2#?}"}" "$2" "${2#?}" "$4" done REPLY="$1$3" } __notice_urlencode() { while test "$1"; do case "$1" in ([a-zA-Z0-9~_.-]*) printf '%.1s' "$1";; # RFC 3986 (*) printf '%%%02X' "'$1";; # bash complains but works esac set -- "${1#?}" done } 2>&- # send bash's complaints to the abyss __notice_generate_id() { # wraps around every 25 years : "${1:?missing seed}" REPLY="" while [ "${#REPLY}" -lt 8 ]; do case "$(($1%13))" in (0) REPLY="${REPLY}a";; (1) REPLY="${REPLY}c";; (2) REPLY="${REPLY}e";; (3) REPLY="${REPLY}m";; (4) REPLY="${REPLY}n";; (5) REPLY="${REPLY}o";; (6) REPLY="${REPLY}r";; (7) REPLY="${REPLY}s";; (8) REPLY="${REPLY}u";; (9) REPLY="${REPLY}v";; (10) REPLY="${REPLY}w";; (11) REPLY="${REPLY}x";; (12) REPLY="${REPLY}z";; esac set -- "$(($1/13))" done } __notice_log() { printf '## [%s](%s)\\n\\nUploaded %s byte%s to %s [(aux)](%s) with hashes:\\n* sha1sum: `%s`' \ "$sanitized" "$normal" "$pretty" "$fancy" "$target" "$aux" "$sha1" } # __notice_upload_to_... # org_c_net_paste 50000000 or 47 MiB (approx) # se_uguu 67108864 or 64 MiB # at_x0 104857600 or 100 MiB # com_cockfile 134217728 or 128 MiB # sh_envs 536870912 or 512 MiB # io_file 2000000000 or 1907 MiB (approx) # at_oshi 5000000000 or 4768 MiB (approx) # com_bashupload 50000000000 or 47 GiB (approx) # net_filebin unlimited? __notice_upload_to_org_c_net_paste() { # defaults: 180 days, permanent (remains after download) # TODO: retrieve deletion key from json response (see docs). # WARNING: there seems to be a bug where files with a hash collision # are not uploaded, and you get someone else's file instead! [ "${bytes:-0}" -le 50000000 ] || return 128 # approx, untested target=paste.c-net.org #normal="$(__notice_curl -gT "$1" "https://$target")" || return normal="$(__notice_curl -g --data-binary "@$filepath" -H "X-FileName: ${1##*/}" "https://$target")" || return normal="https:${normal#*http*:}"; normal="${normal%%[!!-~]*}" aux="$normal" } __notice_upload_to_io_file() { # defaults: 14 days, ephemeral (deletes after 1 download) # wget should use --content-disposition when downloading [ "${bytes:-0}" -le 2000000000 ] || return 128 # approx, untested target=file.io normal="$(__notice_curl -F "file=@$1" "https://$target")" || return [ "${normal#'{"success":true,"status":200,'}" ] || return normal="https:${normal#*\"link\":\"https:}"; normal="${normal%%[\"]*}" aux="$normal" # no direct link, i think it's based on User-Agent } __notice_upload_to_at_oshi() { # defaults: 1 day, semi-permanent (remains up to 1000 downloads?) # configured: 14 days instead # TODO: retrieve admin URL from response (suffixed with " [Admin]") # NOTE: spaces are automatically converted (by the server) to underscores. [ "${bytes:-0}" -le 5000000000 ] || return 128 # approx, untested target=oshi.at normal="$(__notice_curl -fF "f=@$1" -F expire=20160 "https://$target")" || return normal="https:${normal##*DL: http*:}"; normal="${normal%%[!!-~]*}" aux="$normal" } __notice_upload_to_com_bashupload() { # defaults: 3 days, ephemeral (deletes after 1 download) # it also seems to accept `-F file=blah` multipart form-data # note that filenames with spaces are treated as if no filename was given! # TODO: when name contains spaces, replace with underscores and append to URL? [ "${bytes:-0}" -le 50000000000 ] || return 128 # approx, untested target=bashupload.com # https://$target/${${1##*/}// /%20} normal="$(__notice_curl -gT "$1" "https://$target")" || return normal="https:${normal#*http*:}"; normal="${normal%%[!!-~]*}" aux="$normal?download=1" } __notice_upload_to_net_filebin() { # defaults: 7 days, permament (but can be deleted by anyone with the link) # note that the site says 6 days, but this is rounded down. the API shows 7 days. # spaces are converted to underscores, and perhaps other characters are as well. # note that you need curl -L to retrieve the files; they redirect to an s3 store. target=filebin.net __notice_generate_id "$(date -u +%s)" || return aux="https://$target/$REPLY" #normal="$(__notice_curl -gfT "$1" "$aux/$(__notice_urlencode ${1##*/})")" || return normal="$(__notice_curl -gfT "$1" "$aux/")" || return normal="${normal##*\"filename\": \"}"; normal="$aux/${normal%%\"*}" # optional: lock the bin so nobody can edit it, but anyone can still delete it. curl -sSX PUT "$aux" >/dev/null || true } __notice_upload_to_at_x0() { # defaults: 100 days, permanent # note that file retention decreases as file size increases. # 100 MiB files are kept for 3 days, and 0 byte files are kept for 100 days. # filenames are randomized. cannot manually delete nor specify expiry. [ "${bytes:-0}" -le 104857600 ] || return 128 target=x0.at normal="$(__notice_curl -fF "file=@$1" "https://$target")" || return aux="$normal" } __notice_upload_to_se_uguu() { # defaults: 3 hours, permanent # filenames are randomized. cannot manually delete nor specify expiry. [ "${bytes:-0}" -le 67108864 ] || return 128 target=uguu.se normal="$(__notice_curl -fF "files[]=@$1" "https://$target/upload?output=text")" || return aux="$normal" } __notice_upload_to_com_cockfile() { # defaults: 12 hours, permanent # filenames are randomized. cannot manually delete nor specify expiry. [ "${bytes:-0}" -le 134217728 ] || return 128 target=cockfile.com normal="$(__notice_curl -fF "files[]=@$1" "https://$target/upload.php?output=text")" || return aux="$normal" } __notice_upload_to_sh_envs() { # configured: 14 days, permanent (TODO: add "defaults" to match other docs) # does not remember filenames in any capacity, BUT we can tack on our own to the URL. # you can delete files if you extract the X-Token field from the response HTTP headers. # banned MIME types: application/java-archive, application/java-vm [ "${bytes:-0}" -le 536870912 ] || return 128 target=envs.sh normal="$(__notice_curl -fF "file=@$1" -Fsecret= -Fexpires=336 "https://$target")" || return aux="$normal" normal="$normal/${1##*/}" } __notice_retrying() { case $? in (0) return 0;; # https://curl.se/libcurl/c/libcurl-errors.html (127) interrupt=1 __notice_warn "interrupted, exiting." return 0;; (128) __notice_warn "file unsuitable for $target, skipping to next service..." return 1;; (*) __notice_warn "failed to upload to $target, trying next service..." return 1;; esac } __notice_upload() { unset REPLY aux bytes fancy file filepath interrupt normal pretty sanitized sha1 target file="${1:-missing argument}" file="$(readlink -f "$file")" || return 2 # also converts `\`s to `/`s # i have no idea why, but mingw64 curl on msys2 is replacing each unicode codepoint with a question mark. # the irony is that it seems to be properly decoding the UTF-8 encoding and then replacing it all anyway. [ -d /C ] && filepath="$(LC_ALL= cygpath -ws "$file")" || filepath="$file" REPLY="${file##*/}" # ksh and mksh dictate that the characters must be in this order: __notice_escape "$REPLY" '][!#()*+.<>_`{|}\\\-' __notice_escape "$REPLY" '\\"' sanitized="$REPLY" bytes="$(du -b -- "$file")" || return 2 sha1="$(sha1sum -- "$file")" || return 2 bytes="${bytes%%[!!-~]*}" sha1="${sha1%%[!!-~]*}" __notice_commaize "$bytes"; pretty="$REPLY" test "$bytes" -ge 0 || return 2 if [ "$bytes" -ge 10200547328 ]; then fancy="s ($(( (bytes + 536870912) / 1073741824)) GiB)" elif [ "$bytes" -ge 9961472 ]; then fancy="s ($(( (bytes + 524288) / 1048576)) MiB)" elif [ "$bytes" -ge 9728 ]; then fancy="s ($(( (bytes + 512) / 1024)) KiB)" elif [ "$bytes" -ne 1 ]; then fancy="s" fi REPLY= __notice_upload_to_sh_envs "$file" || __notice_retrying || \ __notice_upload_to_net_filebin "$file" || __notice_retrying || \ __notice_upload_to_at_oshi "$file" || __notice_retrying || \ __notice_upload_to_org_c_net_paste "$file" || __notice_retrying || \ __notice_upload_to_io_file "$file" || __notice_retrying || \ __notice_upload_to_com_bashupload "$file" || __notice_retrying || \ { __notice_warn "failed to upload to every service, exiting." return 1 } [ "$interrupt" != 1 ] || return 127 REPLY="$(__notice_log)" return 0 } __notice() { unset LC_ALL REPLY aux code message normal title token url export LC_ALL=C url='https://eaguru.guru/gotify/message?token=' #token="$(cat ~/.gotify.secret)" || return read -r token <~/.gotify.secret test "$token" || return # TODO: give an actual error message. if [ $# != 1 ] && [ $# != 2 ]; then printf 'usage:\n' >&2 printf ' %s %s\n' >&2 "$0" '{message}' "$0" '{title} {message}' "$0" '@{file}' return 64 fi [ $# != 2 ] || { title="$1"; shift ;} message="${1:?missing message}" if [ $# = 1 ] && [ "${message#@}" != "$message" ]; then __notice_upload "${message#@}" || return printf '%s\n' >&2 "$normal" fi set -- -qLo /dev/null -w '%{http_code}' set -- "$@" "$url$token" if [ -n "$REPLY" ]; then set -- "$@" --data '{"title": "New file uploaded", "message": "'"$REPLY"'", "extras": {"client::display": {"contentType": "text/markdown"}, "client::notification": {"click": {"url": "'"$aux"'"}}}}' set -- "$@" -H 'Content-Type: application/json' else [ -z "$title" ] || set -- "$@" --form-string "title=$title" [ -z "$message" ] || set -- "$@" --form-string "message=$message" fi set -- "$@" -H "Accept: application/json" if code="$(__notice_curl "$@")" && [ "$code" = 200 ]; then : else __notice_warn "failed to upload message to ${url%%\?*} (\$code=$code, \$?=$?)" return 1 fi } notice()(__notice "$@") [ -n "${preload+-}" ] || __notice "$@" # cursed modeline: # vim:ft=bash ts=3 sw=3 noet sts=3