Skip to content

Instantly share code, notes, and snippets.

@jcttrll
Last active August 9, 2017 19:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcttrll/28eb7817e07950acdd4b975ee0e453a8 to your computer and use it in GitHub Desktop.
Save jcttrll/28eb7817e07950acdd4b975ee0e453a8 to your computer and use it in GitHub Desktop.
Quick-'n'-dirty shell script to hit the Twitter REST APIs
#!/bin/bash -e
set -o pipefail
fail() {
echo "$*" >&2
return 1
}
verify-utilities-available() {
local util
for util in "$@"; do
which "$util" &>/dev/null ||
fail "ERROR: This script requires the $util utility, which was not found"
done
}
show-usage() {
echo "Usage: twitter.sh -?/--help" >&2
echo " or: twitter.sh" >&2
echo " -m,--http-method <method>" >&2
echo " -u,--url <URL>" >&2
echo " --consumer-key <key>" >&2
echo " --consumer-secret <secret>" >&2
echo " --access-token <oauth-token>" >&2
echo " --access-token-secret <oauth-token-secret>" >&2
echo " [-q,--query-param <key=value>]" >&2
echo " [-b,--body-param <key=value>]" >&2
}
parse-arg() {
local name="$1"
local -n _opts="$2"
shift 2
if [ -n "${_opts[$name]}" ]; then
fail "ERROR: $1 can only be specified once"
fi
if [ $# -lt 2 ]; then
fail "ERROR: $1 requires an argument"
fi
if [ -z "$2" ]; then
fail "ERROR: Argument to $1 cannot be empty"
fi
_opts["$name"]="$2"
}
parse-url-param-arg() {
local -n _assoc="$1"
local key
local value
shift
if [ $# -lt 2 ]; then
fail "ERROR: $1 requires an argument"
fi
if [ -z "$2" ]; then
fail "ERROR: Argument to $1 cannot be empty"
fi
if [[ ! "$2" =~ .= ]]; then
fail "ERROR: Argument to $1 must be key=value pair; got: $2"
fi
key=$(url-encode "${2%%=*}")
value=$(url-encode "${2#*=}")
_assoc+=(["$key"]="$value")
}
require-param() {
local name="$1"
local value="$2"
if [ -z "$value" ]; then
fail "ERROR: $name required"
fi
}
url-encode() {
local raw="$1"
local encoded
encoded=$(while IFS= read -r -n 1 char; do
if [ -z "$char" ]; then
char=$'\n'
fi
case "$char" in
[0-9A-Za-z-._~])
echo -n "$char"
;;
*)
echo -n "$char" | od -t x1 -A n -w4 | sed 's/ /%/g;s/./\U&/g'
;;
esac
done <<< "$raw" | sed ':a;N;s/\n//;ta')
echo "${encoded:0:-3}" # read loop always picks up extra \n at end
}
hex-encode() {
local value="$1"
echo -n "$value" | od -t x1 -A n -w${#value} | sed 's/ //g'
}
hmac-sha1() {
local key
key=$(hex-encode "$1")
local data="$2"
echo -n "$data" | openssl sha1 -binary -mac HMAC -macopt "hexkey:$key" | base64
}
create-query-string() {
local -n _params="$1"
local key
for key in "${!_params[@]}"; do
echo -e "$key=${_params[$key]}"
done | sed ':a;N;s/\n/\&/;ta'
}
create-auth-header() {
local -n _opts="$1"
local -n _query_params="$2"
local -n _body_params="$3"
local consumer_secret
local token_secret
local signature_base
local signing_key
local signature
local header
local -n ref
local key
local -A oauth_headers=(
[oauth_signature_method]=HMAC-SHA1
[oauth_version]=1.0
[oauth_timestamp]=$(date +%s)
[oauth_nonce]=$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | od -t x1 -A n -w16 | sed 's/ //g')
[oauth_consumer_key]="${_opts[oauth_consumer_key]}"
[oauth_token]="${_opts[oauth_token]}"
)
consumer_secret=$(url-encode "${_opts[oauth_consumer_secret]}")
token_secret=$(url-encode "${_opts[oauth_token_secret]}")
signing_key="$consumer_secret&$token_secret"
signature_base="${_opts[http_method]}&$(url-encode "${_opts[url]}")&"
signature_base+=$(url-encode $(
for ref in oauth_headers _query_params _body_params; do
for key in "${!ref[@]}"; do
echo "$key=${ref[$key]}"
done
done | sort -t = -k1,2 | sed ':a;N;s/\n/\&/;ta'
))
signature=$(url-encode $(hmac-sha1 "$signing_key" "$signature_base"))
header="Authorization: OAuth "
for key in "${!oauth_headers[@]}"; do
header+="$key=\"${oauth_headers[$key]}\", "
done
header+="oauth_signature=\"$signature\""
echo "$header"
}
request() {
local -A options
local -A query_params
local -A body_params
local auth_header
local query_string
local body_string
while [ $# -ne 0 ]; do
case "$1" in
-m|--http-method)
parse-arg http_method options "$@"
shift 2
;;
-u|--url)
parse-arg url options "$@"
shift 2
;;
--consumer-key)
parse-arg oauth_consumer_key options "$@"
shift 2
;;
--consumer-secret)
parse-arg oauth_consumer_secret options "$@"
shift 2
;;
--access-token)
parse-arg oauth_token options "$@"
shift 2
;;
--access-token-secret)
parse-arg oauth_token_secret options "$@"
shift 2
;;
-q|--query-param)
parse-url-param-arg query_params "$@"
shift 2
;;
-b|--body-param)
parse-url-param-arg body_params "$@"
shift 2
;;
-?|--help)
show-usage
return 0
;;
*)
fail "ERROR: Unrecognized parameter $1"
;;
esac
done
require-param "-m,--http-method" "${options[http_method]}"
require-param "-u,--url" "${options[url]}"
require-param "--consumer-key" "${options[oauth_consumer_key]}"
require-param "--consumer-secret" "${options[oauth_consumer_secret]}"
require-param "--access-token" "${options[oauth_token]}"
require-param "--access-token-secret" "${options[oauth_token_secret]}"
if [[ "${options[url]}" =~ \? ]]; then
fail "ERROR: URL cannot contain query parameters" \
"(use -q/--query-param); got: ${options[url]}"
fi
auth_header="$(create-auth-header options query_params body_params)"
query_string="$(create-query-string query_params)"
body_string="$(create-query-string body_params)"
curl -X "${options[http_method]}" --http1.1 -sS --fail --connect-timeout 10 --max-time 20 \
-H "Accept-Encoding: identity;q=0.0" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "$auth_header" \
--data-raw "$body_string" \
"${options[url]}?$query_string" |
jq
}
if [ $# -eq 0 ]; then
show-usage
exit 1
fi
verify-utilities-available dd od jq sed openssl base64 curl
request "$@"
@jcttrll
Copy link
Author

jcttrll commented Aug 8, 2017

Usage: twitter.sh -?/--help
  or:  twitter.sh
         -m,--http-method <method>
         -u,--url <URL>
         --consumer-key <key>
         --consumer-secret <secret>
         --access-token <oauth-token>
         --access-token-secret <oauth-token-secret>
         [-q,--query-param <key=value>]
         [-b,--body-param <key=value>]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment