mirror of
https://github.com/notwa/rc
synced 2024-12-21 21:49:40 -08:00
434 lines
16 KiB
Bash
Executable file
434 lines
16 KiB
Bash
Executable file
#!/usr/bin/env sh
|
|
# compat: +ash +bash +dash -hush +ksh +mksh +oksh +osh +posh +yash +zsh
|
|
# hush either fails to output certain text or just segfaults.
|
|
|
|
__notice_warn() {
|
|
printf >&2 'notice: %s\n' "$*"
|
|
}
|
|
|
|
__notice_curl() {
|
|
curl -q --no-progress-meter 127.255.255.255 >/dev/null 2>&1
|
|
if [ $? = 2 ]
|
|
then __notice_curl() { curl -fsS "$@"; }
|
|
else __notice_curl() { curl -f --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_print() {
|
|
# much faster and more portable than the old version.
|
|
# unfortunately, this requires a subshell to capture.
|
|
a="$1"
|
|
until [ -z "$a" ]; do
|
|
case "$a" in
|
|
([$2]*) printf %s%.1s \\ "$a";;
|
|
(*) printf %.1s "$a";;
|
|
esac
|
|
a="${a#?}"
|
|
done
|
|
}
|
|
|
|
__notice_escape() {
|
|
REPLY="$(__notice_escape_print "$@")"
|
|
}
|
|
|
|
__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_urlencode2() {
|
|
while test "$1"; do
|
|
case "$1" in
|
|
([:/a-zA-Z0-9~_.-]*) printf '%.1s' "$1";; # : and / added here
|
|
(*) printf '%%%02X' "'$1";; # bash complains but works
|
|
esac
|
|
set -- "${1#?}"
|
|
done
|
|
} 2>&- # send bash's complaints to the abyss
|
|
|
|
__notice_generate_id() {
|
|
: "${1:?missing seed}"
|
|
unset REPLY
|
|
|
|
case "$1" in
|
|
# when "$(date -u +%s)" is the argument, this will last until 2096.
|
|
(*[!0-9]*) false;; (???????????*) false;; ([4-9]?????????) false;;
|
|
esac || return
|
|
|
|
# use REPLY as a temporary variable, since we'll overwrite it later anyway.
|
|
REPLY="$(( (2250438373 * $1 + 1) & 4294967295 ))" # shuffle IDs
|
|
# all this checking for negatives is due to mksh which overflows an i32.
|
|
REPLY="$(( (REPLY < 0 ? REPLY + 216313691 : REPLY) % 815730721 ))"
|
|
REPLY="$(( REPLY < 0 ? REPLY + 815730721 : REPLY ))"
|
|
|
|
set -- "$REPLY"
|
|
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_...
|
|
# (SLOW) com_cockfile 134217728+??? or 128 MiB (wtf is going on here)
|
|
# (slow) io_file 2000000000-200-4-x or 1907 MiB (untested)
|
|
# sh_envs 536870912-200-4-x or 512 MiB (untested)
|
|
# se_uguu 67108864-200-7-x or 64 MiB (tested)
|
|
# org_c_net_paste 50000000 or 47 MiB (tested)
|
|
# at_oshi 5000000000-200-1-x or 4768 MiB (untested)
|
|
# net_filebin unlimited?
|
|
# com_bashupload 50000000000 or 47 GiB (untested)
|
|
# (fast) at_x0 232783872-200-4-x or 222 MiB (tested) (used to be 100 MiB)
|
|
# (FAST) moe_catbox_litterbox 1000000000 or 1 GB (untested)
|
|
|
|
# NOTE: for sites that use `curl -F` to upload, you automatically lose
|
|
# at least 200 bytes to multipart form-data. names count toward this.
|
|
|
|
# EXAMPLE: curl -F files[]=@myfile https://example.com
|
|
# let's say example.com advertises a file size limit of 1000 bytes.
|
|
# you lose 200, then another 7 for "files[]", then another 6 for "myfile".
|
|
# therefore the maximum size that "myfile" can be is instead 787.
|
|
|
|
# NOTE: i haven't tested double quotes in filenames yet;
|
|
# perhaps they count as two bytes each - not sure.
|
|
|
|
__notice_compute_form_limit() {
|
|
# usage: $0 {FIELD NAME} {FILE NAME} {FILE SIZE} {SIZE LIMIT}
|
|
# returns 1 when over the limit.
|
|
return "$(( ($3) + 200 + (${#1}) + (${#2}) > ($4) ))"
|
|
}
|
|
|
|
__notice_upload_to_org_c_net_paste() {
|
|
target=paste.c-net.org
|
|
# 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
|
|
raw="$(__notice_curl -g --data-binary "@$filepath" -H "X-FileName: ${1##*/}" "https://$target")" || return
|
|
normal="https:${raw#*http*:}"; normal="${normal%%[!!-~]*}"
|
|
aux="$normal"
|
|
}
|
|
|
|
__notice_upload_to_io_file() {
|
|
target=file.io
|
|
# defaults: 14 days, ephemeral (deletes after 1 download)
|
|
# wget should use --content-disposition when downloading
|
|
__notice_compute_form_limit "file" "${1##*/}" "${bytes:-0}" 2000000000 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
raw="$(__notice_curl -F "file=@\"$REPLY\"" "https://$target")" || return
|
|
[ "${raw#'{"success":true,"status":200,'}" ] || return
|
|
normal="https:${raw#*\"link\":\"https:}"; normal="${normal%%[\"]*}"
|
|
aux="$normal" # no direct link, i think it's based on User-Agent
|
|
}
|
|
|
|
__notice_upload_to_at_oshi() {
|
|
target=oshi.at
|
|
# 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.
|
|
__notice_compute_form_limit "f" "${1##*/}" "${bytes:-0}" 5000000000 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
raw="$(__notice_curl -F "f=@\"$REPLY\"" -F expire=20160 "https://$target")" || return
|
|
normal="https:${raw##*DL: http*:}"; normal="${normal%%[!!-~]*}"
|
|
aux="$normal"
|
|
}
|
|
|
|
__notice_upload_to_com_bashupload() {
|
|
target=bashupload.com
|
|
# 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
|
|
# https://$target/${${1##*/}// /%20}
|
|
raw="$(__notice_curl -gT "$1" "https://$target")" || return
|
|
normal="https:${raw#*http*:}"; normal="${normal%%[!!-~]*}"
|
|
aux="$normal?download=1"
|
|
}
|
|
|
|
__notice_upload_to_net_filebin() {
|
|
target=filebin.net
|
|
# 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.
|
|
__notice_generate_id "$(date -u +%s)" || return
|
|
aux="https://$target/$REPLY"
|
|
#raw="$(__notice_curl -gfT "$1" "$aux/$(__notice_urlencode ${1##*/})")" || return
|
|
raw="$(__notice_curl -gT "$1" "$aux/")" || return
|
|
normal="${raw##*\"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() {
|
|
target=x0.at
|
|
# 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.
|
|
__notice_compute_form_limit "file" "${1##*/}" "${bytes:-0}" 232783872 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
raw="$(__notice_curl -F "file=@\"$REPLY\"" "https://$target")" || return
|
|
normal="$raw"
|
|
aux="$normal"
|
|
}
|
|
|
|
__notice_upload_to_se_uguu() {
|
|
target=uguu.se
|
|
# defaults: 3 hours, permanent
|
|
# filenames are randomized. cannot manually delete nor specify expiry.
|
|
# TODO: use fixed filename to squeeze a couple more bytes out of the limit?
|
|
__notice_compute_form_limit "files[]" "${1##*/}" "${bytes:-0}" 67108864 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
raw="$(__notice_curl -F "files[]=@\"$REPLY\"" "https://$target/upload?output=text")" || return
|
|
normal="$raw"
|
|
aux="$normal"
|
|
}
|
|
|
|
__notice_upload_to_com_cockfile() {
|
|
target=cockfile.com
|
|
# defaults: 12 hours, permanent
|
|
# filenames are randomized. cannot manually delete nor specify expiry.
|
|
# must have a file extension or it gets rejected (415), so use `.bin`.
|
|
__notice_compute_form_limit "files[]" ".bin" "${bytes:-0}" 999999999 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
raw="$(__notice_curl -F "files[]=@$\"$REPLY\";filename=.bin" "https://$target/upload.php?output=text")" || return
|
|
normal="$raw"
|
|
aux="$normal"
|
|
}
|
|
|
|
__notice_upload_to_sh_envs() {
|
|
target=envs.sh
|
|
# 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
|
|
__notice_compute_form_limit "file" "${1##*/}" "${bytes:-0}" 536870912 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
raw="$(__notice_curl -F "file=@\"$REPLY\"" -Fsecret= -Fexpires=336 "https://$target")" || return
|
|
aux="$raw"
|
|
normal="$raw/${1##*/}"
|
|
}
|
|
|
|
__notice_upload_to_moe_catbox_litterbox() {
|
|
target=litterbox.catbox.moe
|
|
# defaults: 1 hour, permanent
|
|
# configured: 3 days, permanent
|
|
# filenames are randomized. cannot manually delete nor specify expiry.
|
|
# SUPER fast. disallowed filetypes: .exe, .scr, .cpl, .doc*, .jar
|
|
__notice_compute_form_limit "fileToUpload" "${1##*/}" "${bytes:-0}" 1000000000 || return 128
|
|
__notice_escape "$1" '\\"'
|
|
if [ "${1%.exe}" != "$1" ] || [ "${1%.scr}" != "$1" ]; then
|
|
set -- "$1" "fileToUpload=@\"$REPLY\";filename=\"${REPLY%.???}.com\"" # bypass
|
|
elif [ "${1%.cpl}" != "$1" ]; then
|
|
set -- "$1" "fileToUpload=@\"$REPLY\";filename=\"${REPLY%.???}.dll\"" # bypass
|
|
else
|
|
set -- "$1" "fileToUpload=@\"$REPLY\""
|
|
fi
|
|
raw="$(__notice_curl -F "$2" -Ftime=72h -Freqtype=fileupload "https://$target/resources/internals/api.php")" || return
|
|
normal="$raw"
|
|
aux="$normal"
|
|
}
|
|
|
|
__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 raw 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" '][!#()*+<>_`{|}\\\-' # removed '.' due to discord
|
|
__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_at_oshi "$file" || __notice_retrying ||
|
|
__notice_upload_to_net_filebin "$file" || __notice_retrying ||
|
|
__notice_upload_to_org_c_net_paste "$file" || __notice_retrying ||
|
|
__notice_upload_to_io_file "$file" || __notice_retrying ||
|
|
__notice_upload_to_moe_catbox_litterbox "$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
|
|
normal="$(__notice_urlencode2 "$normal")"
|
|
aux="$(__notice_urlencode2 "$aux")"
|
|
REPLY="$(__notice_log)"
|
|
return 0
|
|
}
|
|
|
|
__notice_read_conf() {
|
|
# TODO: ensure conf file is unreadable by others?
|
|
while read -r line; do
|
|
line="${line%%"#"*}" # strip comments
|
|
line="${line#"${line%%[! ]*}"}" # strip left
|
|
line="${line%"${line##*[! ]}"}" # strip right
|
|
if [ "${line#gotify=}" != "$line" ]; then
|
|
__notice_push_to_gotify "${line#*=}"
|
|
elif [ "${line#discord=}" != "$line" ]; then
|
|
__notice_push_to_discord "${line#*=}"
|
|
elif [ -n "$line" ]; then
|
|
__notice_warn "warning: ignoring line: $line"
|
|
fi
|
|
done < "$1"
|
|
}
|
|
|
|
__notice() {
|
|
unset LC_ALL REPLY aux code conf extras line message normal title token url
|
|
export LC_ALL=C
|
|
conf="${XDG_CONFIG_HOME:-"$HOME/.config"}/notice.conf"
|
|
lament() { printf >&2 %s\\n "$@"; }
|
|
|
|
url='https://eaguru.guru/gotify/message?token='
|
|
if ! [ -e "$conf" ] && [ -e ~/.gotify.secret ]; then
|
|
lament 'notice: warning: ~/.gotify.secret is being phased out in favor of'
|
|
lament ' '"$conf"' containing a list of URLs:'
|
|
lament ' gotify=https://example.com/gotify/message?token=secret'
|
|
read -r token <~/.gotify.secret
|
|
fi
|
|
|
|
if [ $# != 1 ] && [ $# != 2 ]; then
|
|
lament 'usage:'
|
|
printf >&2 ' %s %s\n' "$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
|
|
lament "$normal"
|
|
elif [ $# = 1 ] && [ "$title" = @ ]; then
|
|
__notice_upload "$message" || return
|
|
lament "$normal"
|
|
fi
|
|
|
|
if [ -e "$conf" ]; then
|
|
__notice_read_conf "$conf"
|
|
elif [ -n "$token" ]; then
|
|
__notice_push_to_gotify "$url$token"
|
|
fi
|
|
}
|
|
|
|
__notice_push_to_gotify() {
|
|
url="${1%%\?*}"
|
|
set -- -qLo /dev/null -w '%{http_code}' "$@"
|
|
if [ -n "$REPLY" ]; then
|
|
extras='{"client::display": {"contentType": "text/markdown"}, "client::notification": {"click": {"url": "'"$aux"'"}}}'
|
|
set -- "$@" --data '{"title": "New file uploaded", "message": "'"$REPLY"'", "extras": '"$extras"'}'
|
|
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 push message to $url (\$code=$code, \$?=$?)"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
__notice_push_to_discord() {
|
|
# flags: 2 (SUPPRESS_EMBEDS)
|
|
url="${1%%\?*}"
|
|
set -- -qLo /dev/null -w '%{http_code}' "$@"
|
|
if [ -n "$REPLY" ]; then
|
|
set -- "$@" --data '{"content": "# New file uploaded\n\n'"$REPLY"'", "flags": 2}'
|
|
elif [ -z "$title" ]; then
|
|
set -- "$@" --data '{"content": "'"$message"'", "flags": 2}'
|
|
elif [ -z "$message" ]; then
|
|
set -- "$@" --data '{"content": "# '"$title"'", "flags": 2}'
|
|
else
|
|
set -- "$@" --data '{"content": "# '"$title"'\n\n'"$message"'", "flags": 2}'
|
|
fi
|
|
set -- "$@" -H 'Content-Type: application/json'
|
|
set -- "$@" -H "Accept: application/json"
|
|
|
|
if code="$(__notice_curl "$@")" && { [ "$code" = 200 ] || [ "$code" = 204 ]; }; then
|
|
:
|
|
else
|
|
__notice_warn "failed to push 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
|