#!/bin/bash
# $Id: twimsg.in 32 2013-09-21 17:47:24Z archie.cobbs $

# Bail on error
set -e

# Constants
DEFAULT_CONFIG_FILE='/etc/twilio.conf'
DEFAULT_TRUNCATE_LIMIT='140'
BASE_URL='https://api.twilio.com/'
ACCOUNTS_PATH='/2010-04-01/Accounts/${ACCOUNT_SID}/Messages'
RESULT_XSL='/usr/share/twilio-utils/result.xsl'
CURL="/usr/bin/curl"
ICONV="/usr/bin/iconv"
XSLTPROC="/usr/bin/xsltproc"
DEFAULT_ENCODING="UTF-8"
TWILIO_ENCODING="UTF-8"
SMSLEN="/usr/bin/smslen"

# Usage message
usage()
{
    echo "Usage: twimsg [-c config.txt] [-C curlflag] [-e encoding] [-F from-number] [-f file] [-m url] [-t limit] number" 1>&2
    echo "Options:" 1>&2
    echo "    -C    Pass curlflag to curl(1)" 1>&2
    echo "    -c    Specify config file (default \"${DEFAULT_CONFIG_FILE}\")" 1>&2
    echo "    -e    Specify input encoding (default \"${DEFAULT_ENCODING}\")" 1>&2
    echo "    -f    Read message from file (\`-' means stdin (the default))" 1>&2
    echo "    -m    Include the image at the specified URL via MMS (may be repeated)" 1>&2
    echo "    -F    Specify sending phone number (10 digits)" 1>&2
    echo "    -t    Truncate input at limit bytes (default ${DEFAULT_TRUNCATE_LIMIT})" 1>&2
}

# Function to normalize a phone number to the way Twilio likes it
normalize()
{
    echo ${1+"$@"} | sed -r \
      -e 's/[^0-9]//g' \
      -e 's/^1?([0-9]{10})$/+1\1/g'
}

# Parse flags passed in on the command line
CONFIG_FILE="${DEFAULT_CONFIG_FILE}"
ENCODING="${DEFAULT_ENCODING}"
TRUNCATE_LIMIT="${DEFAULT_TRUNCATE_LIMIT}"
FROM_NUMBER=""
INPUT_FILE="-"
CURL_FLAGS=""
while [ ${#} -gt 0 ]; do
    case "$1" in
        -c)
            shift
            CONFIG_FILE="${1}"
            shift
            ;;
        -C)
            shift
            CURL_FLAGS="${CURL_FLAGS} ${1}"
            shift
            ;;
        -e)
            shift
            ENCODING="${1}"
            shift
            ;;
        -f)
            shift
            INPUT_FILE="${1}"
            shift
            ;;
        -F)
            shift
            FROM_NUMBER="${1}"
            shift
            ;;
        -m)
            shift
            IMAGE_URL="${1}"
            if ! [[ "${IMAGE_URL}" =~ ^https?://[^[:space:]]+$ ]]; then
                echo "twimsg: invalid image URL \`${IMAGE_URL}'" 1>&2
                exit 1
            fi
            shift
            CURL_FLAGS="${CURL_FLAGS} --data-urlencode MediaUrl=${IMAGE_URL}"
            ;;
        -t)
            shift
            TRUNCATE_LIMIT="${1}"
            shift
            ;;
        -h|--help)
            usage
            exit
            ;;
        --)
            shift
            break
            ;;
        *)
            break
            ;;
    esac
done
case "${#}" in
    1)
        TO_NUMBER="$1"
        ;;
    *)
        usage
        exit 1
        ;;
esac

# See if config file is readable
if ! test -r "${CONFIG_FILE}"; then
    echo "twimsg: can't read ${CONFIG_FILE}" 1>&2
    exit 1
fi

# Parse config file
. "${CONFIG_FILE}"

# Normalize numbers
FROM_NUMBER=`normalize "${FROM_NUMBER}"`
TO_NUMBER=`normalize "${TO_NUMBER}"`

# Sanity check stuff
if ! [[ "${FROM_NUMBER}" =~ ^\+1[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}$ ]]; then
    echo "twimsg: invalid source phone number \`${FROM_NUMBER}'" 1>&2
    exit 1
fi
if ! [[ "${TO_NUMBER}" =~ ^\+1[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}$ ]]; then
    echo "twimsg: invalid destination phone number \`${TO_NUMBER}'" 1>&2
    exit 1
fi
if ! [[ "${ACCOUNT_SID}" =~ ^AC[0-9a-f]{32}$ ]]; then
    echo "twimsg: invalid account SID \`${ACCOUNT_SID}'" 1>&2
    exit 1
fi
if ! [[ "${AUTH_TOKEN}" =~ ^[0-9a-f]{32}$ ]]; then
    echo "twimsg: invalid authentication token" 1>&2
    exit 1
fi

# Build URL
URL="`echo ${BASE_URL} | sed 's|/$||g'`""`eval echo ${ACCOUNTS_PATH}`"

# Create temporary files for message response and error
RESPONSE_FILE=`mktemp -q /tmp/twimsg.XXXXXX`
if [ $? -ne 0 ]; then
    echo "twimsg: can't create temporary file" 1>&2
    exit 1
fi
ERROR_FILE=`mktemp -q /tmp/twimsg.XXXXXX`
if [ $? -ne 0 ]; then
    rm -f ${RESPONSE_FILE}
    echo "twimsg: can't create temporary file" 1>&2
    exit 1
fi
trap "rm -f ${RESPONSE_FILE} ${ERROR_FILE}" 0 2 3 5 10 13 15

# Truncate input and post to Twilio
cat "${INPUT_FILE}" \
  | "${ICONV}" -c -s -f "${ENCODING}" -t "${TWILIO_ENCODING}" \
  | "${SMSLEN}" -i "${TWILIO_ENCODING}" -t "${TRUNCATE_LIMIT}" \
  | "${CURL}" --silent \
  --user "${ACCOUNT_SID}:${AUTH_TOKEN}" \
  --data "From=${FROM_NUMBER}" \
  --data "To=${TO_NUMBER}" \
  --data-urlencode "Body@-" \
  --output "${RESPONSE_FILE}" \
  ${CURL_FLAGS} \
  "${URL}"

# Check result
CURL_RESULT="$?"
if [ "${CURL_RESULT}" -ne 0 ]; then
    echo "twimsg: error sending request (curl returned ${CURL_RESULT})" 1>&2
    exit 1
fi

# Apply XSLT to returned XML to get error message
"${XSLTPROC}" "${RESULT_XSL}" "${RESPONSE_FILE}" > "${ERROR_FILE}" 2>&1
if [ $? -ne 0 ]; then
    echo -n "twimsg: error parsing result:" 1>&2
    cat "${ERROR_FILE}" 1>&2
    exit 1
fi

# Was there an error returned?
if [ -s "${ERROR_FILE}" ]; then
    echo -n "twimsg: " 1>&2
    cat "${ERROR_FILE}" 1>&2
    exit 1
fi

# Done
exit 0

