You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tekup 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #!/usr/bin/env bash
  2. declare verbose=0
  3. declare expire_length
  4. declare request_deletion_key
  5. declare clipboard
  6. declare -r conf_dir="${XDG_CONFIG_DIR:-$HOME/.config}/tekup"
  7. declare -r log_file="${conf_dir}/log"
  8. declare -r config_file="${conf_dir}/conf"
  9. declare -A colors
  10. colors[red]=$(tput setaf 1)
  11. colors[reset]=$(tput sgr0)
  12. err() {
  13. printf "${colors[red]}%s${colors[reset]}\n" "$*" >&2
  14. }
  15. die() {
  16. [[ -n "$1" ]] && err "$1"
  17. exit 1
  18. }
  19. usage() {
  20. LESS=-FEXR less <<-HELP
  21. Usage: tekup [OPTIONS] [FILES...]
  22. Upload files to https://teknik.io
  23. OPTIONS:
  24. -d request a deletion key for images
  25. -e <STRING> expiration time. only has an effect on text pastes.
  26. must be in the form of 'N UNIT' where N is a number
  27. and UNIT is one of the following:
  28. view minute hour day month year
  29. -N disables expiring pastes (if enabled in config file, for example)
  30. -c save the url to the clipboard
  31. -h print this help
  32. -v print verbose output. can be stacked. there are three levels of
  33. verbosity (because this thing is over-engineered)
  34. first will show progress from curl
  35. second will print the json response
  36. third will show verbose output from curl
  37. CONFIGURATION:
  38. A configuration file can be defined at ${config_file/$HOME/\~}
  39. If a line begins with '#' it is treated as a comment and ignored.
  40. A configuration file has the following options:
  41. username teknik.io username
  42. api_key teknik.io api key
  43. expire_length same as -e flag above
  44. do_not_track boolean, asks teknik.io not to log the upload
  45. verbose_level must be a number
  46. clipboard boolean
  47. If ${log_file/$HOME/\~} is a writeable file tekup will always ask for a
  48. deletion key and log it in that file.
  49. HELP
  50. }
  51. has() {
  52. local _verbose
  53. if [[ $1 = '-v' ]]; then
  54. _verbose=1
  55. shift
  56. fi
  57. for c; do c="${c%% *}"
  58. if ! command -v "$c" &> /dev/null; then
  59. (( _verbose > 0 )) && err "$c not found"
  60. return 1
  61. fi
  62. done
  63. }
  64. select_from() {
  65. local cmd='command -v'
  66. for a; do
  67. case "$a" in
  68. -c) cmd="$2"; shift 2 ;;
  69. esac
  70. done
  71. for c; do
  72. if $cmd "${c%% *}" &> /dev/null; then
  73. echo "$c"
  74. return 0
  75. fi
  76. done
  77. return 1
  78. }
  79. parse_config_file() {
  80. local line key val nr=0
  81. while IFS= read -r line; do
  82. (( ++nr ))
  83. [[ -z "$line" || "$line" = '#'* ]] && continue
  84. read -r key <<< "${line%% *}"
  85. read -r val <<< "${line#* }"
  86. if [[ -z "$val" ]]; then
  87. config_err+=( "missing value for \"$key\" in config file on line $nr" )
  88. continue
  89. fi
  90. case "$key" in
  91. expire_length) expire_length="$val" ;;
  92. username) username="$val" ;;
  93. api_key) api_key="$val" ;;
  94. do_not_track) do_not_track="$val" ;;
  95. verbose_level) verbose="$val" ;;
  96. clipboard) [[ $val = 'true' ]] && clipboard=1 ;;
  97. *) config_err+=( "unknown key \"$key\" in config file on line $nr" )
  98. esac
  99. done < "$config_file"
  100. if (( ${#config_err[@]} > 0 )); then
  101. err 'there were errors parsing config file:'
  102. for e in "${config_err[@]}"; do
  103. err " $e"
  104. done
  105. fi
  106. }
  107. upload_text() {
  108. local file curl_opts expire_unit
  109. file="$1"
  110. shift
  111. curl_opts=()
  112. case "$verbose" in
  113. 0) curl_opts+=( -s ) ;;
  114. 1|2) curl_opts+=( -# ) ;;
  115. *) curl_opts+=( -v ) ;;
  116. esac
  117. [[ -n "$username" && -n "$api_key" ]] &&
  118. curl_opts+=( -u "${username}:${api_key}" )
  119. if [[ -n "$expire_length" ]]; then
  120. printf -v expire_l '%d' "${expire_length% *}"
  121. expire_unit="${expire_length#* }"
  122. curl_opts+=( --data "expireLength=${expire_l}&expireUnit=${expire_unit/%s}")
  123. fi
  124. curl_opts+=( ${do_not_track:+ --data "doNotTrack=${do_not_track}"} )
  125. curl_opts+=(
  126. --data "title=${file##*/}"
  127. --data-urlencode "code=$(< "$file")"
  128. https://api.teknik.io/v1/Paste )
  129. curl "${curl_opts[@]}" | parse_response
  130. }
  131. upload_file() {
  132. local file mime curl_opts
  133. file="$1"
  134. mime="$2"
  135. shift 2
  136. curl_opts=()
  137. case "$verbose" in
  138. 0) curl_opts+=( -s ) ;;
  139. 1|2) curl_opts+=( -# ) ;;
  140. *) curl_opts+=( -v ) ;;
  141. esac
  142. [[ -n "$username" && -n "$api_key" ]] &&
  143. curl_opts+=( -u "${username}:${api_key}" )
  144. curl_opts+=( ${do_not_track:+ -F "doNotTrack=${do_not_track}"} )
  145. [[ -w "$log_file" || -n "$request_deletion_key" ]] &&
  146. curl_opts+=( -F 'genDeletionKey=true' )
  147. curl_opts+=( -F "contentType=${mime%%;*}"
  148. -F "file=@\"${file}\""
  149. https://api.teknik.io/v1/Upload )
  150. curl "${curl_opts[@]}" | parse_response
  151. }
  152. upload_files() {
  153. local f mime
  154. for f; do
  155. if [[ "$f" = '-' ]]; then
  156. echo 'reading from stdin'
  157. tee /tmp/tekup_paste &> /dev/null
  158. f=/tmp/tekup_paste
  159. fi
  160. if [[ ! -e "$f" ]]; then
  161. err "$f does not exist"
  162. continue
  163. fi
  164. mime=$(file -Lib "$f")
  165. if [[ "${mime%%/*}" = 'text' ]]; then
  166. upload_text "$f"
  167. else
  168. upload_file "$f" "$mime"
  169. fi
  170. done
  171. }
  172. parse_response() {
  173. local url deletion_key expire_date log_msg
  174. read -r response
  175. (( verbose > 1 )) && jq <<< "$response"
  176. if [[ "$response" != *'http'* || -z "$response" || "$response" = *'error'* ]]; then
  177. err "error uploading $file"
  178. die "$(jq -r '.error.message' <<< "$response")"
  179. fi
  180. url=$(jq -r '.result.url' <<< "$response")
  181. url="${url/paste.teknik.io/p.teknik.io\/Raw}"
  182. url="${url/upload/u}"
  183. file="${file/\/tmp\/tekup_paste/stdin}"
  184. printf '%s: %s\n' "$file" "$url"
  185. if [[ -n "$clipboard" ]]; then
  186. prg=$(select_from 'xclip -r ' 'xsel -b')
  187. if [[ -n "$prg" ]]; then
  188. $prg <<< "$url"
  189. else
  190. err 'xclip or xsel required for saving to clipboard'
  191. fi
  192. fi
  193. if [[ -w "$log_file" || -n "$request_deletion_key" ]]; then
  194. deletion_key=$(jq -r '.result.deletionKey' <<< "$response")
  195. [[ "$deletion_key" = null ]] && deletion_key=''
  196. [[ -n "$deletion_key" ]] && printf 'deletion key: %s/%s\n' "$url" "$deletion_key"
  197. fi
  198. if [[ -n "$expire_length" ]]; then
  199. expire_date=$(jq -r '.result.expiration | (match("[0-9]+").string)? // .result.expiration' <<< "$response")
  200. [[ "$expire_date" = null ]] && expire_date=''
  201. [[ -n "$expire_date" ]] &&
  202. printf 'expires at: %s\n' "$(date -d "@$expire_date")"
  203. fi
  204. if [[ -w "$log_file" ]]; then
  205. log_msg="$(date +%s) | file: $file | url: $url"
  206. [[ -n "$expire_date" ]] &&
  207. log_msg+=" | expires: $expire_date"
  208. [[ -n "$deletion_key" ]] &&
  209. log_msg+=" | delete: $url/$deletion_key"
  210. echo "$log_msg" >> "$log_file"
  211. fi
  212. }
  213. finish() {
  214. [[ -e /tmp/tekup_paste ]] && rm /tmp/tekup_paste
  215. }
  216. trap finish SIGHUP SIGINT SIGTERM
  217. has -v curl jq || die
  218. [[ -s "$config_file" ]] && parse_config_file
  219. OPTERR=0
  220. while getopts 'hve:dNc' opt; do
  221. case "$opt" in
  222. h) usage; exit 0 ;;
  223. v) (( ++verbose )) ;;
  224. d) request_deletion_key=1 ;;
  225. e) expire_length="$OPTARG" ;;
  226. N) expire_length='' ;;
  227. c) clipboard=1 ;;
  228. esac
  229. done
  230. shift $(( OPTIND - 1 ))
  231. unset opt OPTARG OPTIND
  232. (( $# > 0 )) || die 'No files to upload.'
  233. upload_files "$@"
  234. finish