#!/usr/bin/python
"""Send SMS via clickatell HTTPS API

Further details on the key/values used by clickatell's SMS gateway can
be found in their API documentation.

Note: on error the exitcode is set to clickatell's error id.

Options:
    --stdin         read text from stdin (by default reads from cli)
    --maxlen=LEN    restrict text length to LEN

Environment variables (can also be specified on command line):

  CLICKATELL_USER
  CLICKATELL_PASSWORD
  CLICKATELL_API_ID

Resolution order:

  1) command line (highest precedence)
  2) environment (lowest precedence)

Example usage:

    export CLICKATELL_USER=liraz
    export CLICKATELL_PASSWORD=secretpass
    export CLICKATELL_API_ID=666

    clickatell-sendmsg to=0542013512 text="evil sms message"

"""
import os
import sys
import urllib
import re
from subprocess import *
import getopt

def usage(e=None):
    if e:
        print >> sys.stderr, "error: " + str(e)

    print >> sys.stderr, "syntax: %s [-options] key=val ..." % sys.argv[0]
    print __doc__.strip()
    sys.exit(1)

class CurlError(Exception):
    pass

def curl(url):
    p = Popen(["curl", url], stdout=PIPE, stderr=PIPE)
    stdout, stderr = p.communicate()
    if p.returncode != 0:
        raise CurlError(p.returncode, stderr.strip())

    return stdout

class SendmsgError(Exception):
    pass

def sendmsg(params):
    url = "https://api.clickatell.com/http/sendmsg?" + urllib.urlencode(params)
    output = curl(url)
    m = re.match(r'ERR: (\d+), (.*)', output)
    if m:
        errno = int(m.group(1))
        errstr = m.group(2)

        raise SendmsgError(errno, errstr)

    m = re.match(r'ID: (.*)', output)
    if m:
        msgid = m.group(1)
        return msgid

    # maybe raise an exception instead?
    return output

def fmt_oneline(input):
    # strip linebreaks and consecutive whitespace
    return re.sub(r'\s+', ' ', re.sub(r'\r?\n', ' ', input)).strip()

CONF_KEYS = ('user', 'password', 'api_id')
def main():
    maxlen = None
    stdin_input = None

    opts, args = getopt.gnu_getopt(sys.argv[1:], "h", ["stdin", "maxlen="])
    for opt, val in opts:
        if opt == "-h":
            usage()

        if opt == "--maxlen":
            maxlen = int(val)

        if opt == "--stdin":
            stdin_input = fmt_oneline(sys.stdin.read())

    if not args:
        usage()

    params = {}
    for key in CONF_KEYS:
        env_name = 'CLICKATELL_' + key.upper()
        if env_name in os.environ:
            params[key] = os.environ[env_name]

    params.update(dict([ arg.split("=", 1) for arg in args ]))

    if stdin_input:
        if 'text' in params:
            usage("--stdin conflicting with 'text' key value argument")

        params['text'] = stdin_input

    for key in CONF_KEYS:
        if key not in params:
            usage("missing needed conf option (%s)" % key)

    if maxlen and len(params['text']) > maxlen:
        params['text'] = params['text'][:maxlen]

    try:
        msgid = sendmsg(params)
    except SendmsgError, e:
        print >> sys.stderr, "error: " + e[1]
        sys.exit(e[0])

    print msgid

if __name__ == "__main__":
    main()
