Skywalker13

Diary of an ex-GeeXboX developer…

Adding tweets with SHell scripting

Posted at — Jul 27, 2013

The last week, I worked on a very small project. Add tweets when a new directory is added somewhere (with a Synology). For this functionality, I added a CRON job that calls a SHell script which does the job. The interresting part is how to sign and call the twitter webservice (the twitter documentation is not always very efficient).

You have two secret keys and two identifiers with your twitter account (for the consumer and the access). Both are necessary for the call to the webservice. First, look the following script …

The whole script

#!/bin/sh

WEBSERVICE="https://my.webservice.ch/foobar.php?"
WEBUSER="myuser"
WEBPWD="mypassword"

DIR="/directory"

CONSUMERKEY="MyTwitterConsumerKey"
CONSUMERSECRET="MyTwitterConsumerSecret"
ACCESSTOKEN="MyTwitterAccessToken"
ACCESSTOKENSECRET="MyTwitterAccessTokenSecret"

[ ! -f "./tmp" ] && echo "no initial list file" && exit 1

mv tmp tmp.old
ls "$DIR" >tmp

curl -s --insecure "${WEBSERVICE}f=Authenticate&p0=${WEBUSER}&p1=${WEBPWD}" -c .cookie >/dev/null

for b in `diff tmp.old tmp | grep '^+' | sed 's,+,,'`; do
  JSON=`curl -s --insecure "${WEBSERVICE}f=FindText&p0=$b" -b .cookie`

  echo $JSON 2>&1 | ./jq '.myWebservice.result1' 2>&1 | grep '^jq: error:' >/dev/null
  if [ "$?" = 0 ]; then
    continue
  fi

  text1=`echo $JSON | ./jq '.myWebservice.result2' | sed 's,",,g'`
  text2=`echo $JSON | ./jq '.myWebservice.result3' | sed 's,",,g'`

  # compute the random value of 32 bytes
  RDM="`dd if=/dev/urandom bs=64 count=1 2>/dev/null | openssl base64 2>/dev/null`"
  RDMENC="`echo $RDM | sed -e 's,+,,g' -e 's,/,,g' | cut -c1-32`"

  # current timestamp
  TIMESTAMP=`date +%s`

  # twitter message (limit the text1 to 70 chars, otherwise we can easily exceed 140 chars)
  title=`echo $text1 | cut -c1-70`
  TEXT="Something long \"$text1...\" something short $text2 #MyTwitter"
  TEXT="`perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$TEXT" 2>/dev/null`"
  TEXTSIGN="`perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$TEXT" 2>/dev/null`"

  # compute the signature
  MSG="POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&oauth_consumer_key%3D${CONSUMERKEY}%26oauth_nonce%3D${RDMENC}%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D${TIMESTAMP}%26oauth_token%3D${ACCESSTOKEN}%26oauth_version%3D1.0%26status%3D${TEXTSIGN}"
  SIGN=`echo -n $MSG | openssl sha1 -binary -hmac "${CONSUMERSECRET}&${ACCESSTOKENSECRET}" 2>/dev/null | openssl base64 2>/dev/null | sed -e s'/+/%2B/' -e s'/\//%2F/' -e s'/=/%3D/'`

  curl --insecure \
       --request 'POST' 'https://api.twitter.com/1.1/statuses/update.json' \
       --data "status=$TEXT" \
       --header "Authorization: OAuth oauth_consumer_key=\"${CONSUMERKEY}\", oauth_nonce=\"${RDMENC}\", oauth_signature=\"$SIGN\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"$TIMESTAMP\", oauth_token=\"${ACCESSTOKEN}\", oauth_version=\"1.0\""

  sleep 5
done

curl -s --insecure "${WEBSERVICE}f=Disconnect" -b .cookie >/dev/null

More details

This script does all the job in order to post a new tweet. Let me explain in details… I use my own webservice in order to retrieve some texts for the tweet. This part is not very interesting for you. The twitter part begins with the random number of 32 bytes.

# compute the random value of 32 bytes
RDM="`dd if=/dev/urandom bs=64 count=1 2>/dev/null | openssl base64 2>/dev/null`"
RDMENC="`echo $RDM | sed -e 's,+,,g' -e 's,/,,g' | cut -c1-32`"

I use base64 on the result from /dev/urandom because we must have only alphanumeric characters (and a length of 32 bytes). Then we can retrieve the current UNIX timestamp:

# current timestamp
TIMESTAMP=`date +%s`

And I prepare the text “status” for the tweet:

# twitter message (limit the text1 to 70 chars, otherwise we can easily exceed 140 chars)
title=`echo $text1 | cut -c1-70`
TEXT="Something long \"$text1...\" something short $text2 #MyTwitter"
TEXT="`perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$TEXT" 2>/dev/null`"
TEXTSIGN="`perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$TEXT" 2>/dev/null`"

I escape a first time the TEXT with the percent. Then TEXT will be used in the call for the twitter webservice. But for the signature, we must escape this string a second time. When the spaces are converted to %20 in the first escape, the spaces are %2520 in the second string. Hey, you have only 140 characters for your message… Then we can compute the signature with OpenSSL.

# compute the signature
MSG="POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&oauth_consumer_key%3D${CONSUMERKEY}%26oauth_nonce%3D${RDMENC}%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D${TIMESTAMP}%26oauth_token%3D${ACCESSTOKEN}%26oauth_version%3D1.0%26status%3D${TEXTSIGN}"
SIGN=`echo -n $MSG | openssl sha1 -binary -hmac "${CONSUMERSECRET}&${ACCESSTOKENSECRET}" 2>/dev/null | openssl base64 2>/dev/null | sed -e s'/+/%2B/' -e s'/\//%2F/' -e s'/=/%3D/'`

Now we have the signature then we can call the twitter webservice. Simply…

curl --insecure \
     --request 'POST' 'https://api.twitter.com/1.1/statuses/update.json' \
     --data "status=$TEXT" \
     --header "Authorization: OAuth oauth_consumer_key=\"${CONSUMERKEY}\", oauth_nonce=\"${RDMENC}\", oauth_signature=\"$SIGN\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"$TIMESTAMP\", oauth_token=\"${ACCESSTOKEN}\", oauth_version=\"1.0\""