Skip to content
Browse files

Added Dart port

  • Loading branch information...
1 parent 9f6a7ba commit 17237f1d91c14a07089def7bfa76fb36b8c5e34f @srackham committed Oct 14, 2012
Showing with 241 additions and 6 deletions.
  1. +4 −4 Jakefile.coffee
  2. +6 −2 README.asciidoc
  3. +231 −0 clisms.dart
View
8 Jakefile.coffee
@@ -20,19 +20,19 @@ task 'default', -> jake.exec ['jake -T'], EXEC_PRINT_OPTS
# Used internally to compile JavaScript executable.
# Invoked with compile command string.
-task 'build', async: true, ['validate'], (compile_cmd)->
+task '_build', async: true, ['validate'], (compile_cmd)->
jake.exec [compile_cmd], ->
addShebang JS_EXE
complete()
, EXEC_PRINT_OPTS
desc 'Build JavaScript executable from CoffeeScript source.'
task 'build.coffee', ->
- jake.Task['build'].invoke "coffee -c '#{CS_SRC}'"
+ jake.Task['_build'].invoke "coffee -c '#{CS_SRC}'"
desc 'Build JavaScript executable from TypeScript source.'
task 'build.ts', ->
- jake.Task['build'].invoke "tsc '#{TS_SRC}'"
+ jake.Task['_build'].invoke "tsc '#{TS_SRC}'"
desc 'Validate package.json.'
task 'validate', ->
@@ -52,7 +52,7 @@ task 'publish', async: true, [PUBLISHED_BUILD], ->
complete()
, breakOnError: false, printStdout: true, printStderr: true
-desc 'Commit with message in file COMMIT.'
+desc 'Commit to git with message in file COMMIT'
task 'commit', [PUBLISHED_BUILD], ->
jake.exec ['git commit -a -F COMMIT'], EXEC_PRINT_OPTS
View
8 README.asciidoc
@@ -61,8 +61,12 @@ home directory. For example:
=== Implementation
The original version this program was
https://srackham.wordpress.com/2010/03/23/command-line-sms-script/[written
-in Python] but this version is written in both CoffeeScript
-(`clisms.coffee`) and TypeScript (`clisms.ts`) as a learning exercise.
+in Python] but this version is written in CoffeeScript
+(`clisms.coffee`), TypeScript (`clisms.ts`) and Dart (`clisms.dart`) as a learning exercise.
+
The compiled node executable `clisms.js` file can be generated from
either version (use the `jake build-coffee` command to compile
`clisms.coffee`, `jake build-ts` command to compile `clisms.ts`.
+
+The Dart version should be run using the command-line Dart VM (`dart
+clisms.dart`), I got errors compiling it with `dart2js`.
View
231 clisms.dart
@@ -0,0 +1,231 @@
+import 'dart:io';
+import 'dart:json';
+import 'dart:uri';
+
+MAN_PAGE() => '''
+
+NAME
+ ${PROG} - Send SMS message
+
+SYNOPSIS
+ ${PROG} PHONE MESSAGE
+ ${PROG} -s MSGID
+ ${PROG} -b | -l | -p
+
+DESCRIPTION
+ A simple command-line script to send SMS messages using
+ Clickatell's HTTP API (see http://clickatell.com).
+ Records messages log in ${LOG_FILE}.
+ Reads configuration parameters from ${CONF_FILE}
+ Version ${VERSION} executed from Dart source.
+
+OPTIONS
+ -s MSGID
+ Query message delivery status.
+
+ -b
+ Query account balance.
+
+ -l
+ List message log file using ${PAGER}.
+
+ -p
+ List phone book.
+
+AUTHOR
+ Written by Stuart Rackham, <srackham@gmail.com>
+
+COPYING
+ Copyright (C) 2012 Stuart Rackham. Free use of this software is
+ granted under the terms of the MIT License.
+''';
+
+const VERSION = '0.4.1';
+
+// Clickatell account configuration parameters.
+// The configuration file is single JSON formatted object with the
+// same attributes and attribute types as the default CONF variable below.
+// Alternatively you could dispense with the configuration file and edit the
+// values in the CONF variable below.
+final CONF = {
+ 'USERNAME': '',
+ 'PASSWORD': '',
+ 'API_ID': '',
+ 'SENDER_ID': '', // Your registered mobile phone number.
+ 'PHONE_BOOK': {}
+};
+
+
+int ARGC;
+List<String> ARGV;
+String PROG;
+String HOME;
+String PAGER;
+Path LOG_FILE;
+Path CONF_FILE;
+
+void main() {
+ Options OPTS = new Options();
+ ARGV = OPTS.arguments;
+ ARGC = ARGV.length;
+ PROG = new Path(OPTS.script).filename;
+ HOME = Platform.environment['HOME'];
+ HOME = HOME == null ? Platform.environment['HOMEPATH'] : HOME;
+ PAGER = Platform.environment['PAGER'];
+ if (PAGER == null) PAGER = 'less';
+ LOG_FILE = new Path(HOME).join(new Path('clisms.log'));
+ CONF_FILE = new Path(HOME).join(new Path('.clisms.json'));
+ var f = new File.fromPath(CONF_FILE);
+ if (f.existsSync()) {
+ CONF = JSON.parse(f.readAsTextSync());
+ }
+
+ if (ARGC == 2) {
+ if (ARGV[0] == '-s')
+ printMessageStatus(ARGV[1]);
+ else
+ sendMessage(ARGV[0], ARGV[1]);
+ }
+ else if (ARGC == 1) {
+ switch (ARGV[0]) {
+ case '-b':
+ printAccountBalance();
+ break;
+ case '-l': // View log file in pager.
+ shell(PAGER, [LOG_FILE.toString()], (exitCode) => exit(exitCode));
+ break;
+ case '-p':
+ CONF['PHONE_BOOK'].forEach(
+ (name, number) => print('${name}: ${number}')
+ );
+ break;
+ default:
+ die('Illegal option: ${ARGV[0]}');
+ }
+ }
+ else
+ print(MAN_PAGE());
+}
+
+// URL query string parameters.
+final QUERY = {
+ 'user': CONF['USERNAME'],
+ 'password': CONF['PASSWORD'],
+ 'api_id': CONF['API_ID'],
+ 'concat': 3,
+ 'req_feat': 32 // Gateway must support numeric sender ID.
+};
+
+// Clickatell status messages.
+final MSG_STATUS = {
+ '001': 'message unknown',
+ '002': 'message queued',
+ '003': 'delivered to gateway',
+ '004': 'received by recipient',
+ '005': 'error with message',
+ '007': 'error delivering message',
+ '008': 'OK',
+ '009': 'routing error',
+ '012': 'out of credit'
+};
+
+// Execute shell command. 'cmd' is executable path; 'args' is arguments array;
+// 'onExit' is executed and passed the process exit code when the command
+// exits.
+void shell(String cmd, List<String> args, void onExit(int exitCode)) {
+ var p = Process.start(cmd, args);
+ p.stdout.pipe(stdout);
+ stdin.pipe(p.stdin);
+ p.onExit = (exitCode) {
+ p.close();
+ onExit(exitCode);
+ };
+}
+
+/**
+Execute Clickatell HTTP command.
+The `processResponse` function is called with the HTTP response data string.
+*/
+void httpCmd(String cmd, void processResponse(String data)) {
+ // Encode URI query parameters.
+ var params = [];
+ QUERY.forEach((k, v) {
+ params.add('${encodeUriComponent(k.toString())}='
+ '${encodeUriComponent(v.toString())}');
+ });
+ var query = Strings.join(params, '&');
+ var httpClient = new HttpClient();
+ HttpClientConnection conn = httpClient.get(
+ 'api.clickatell.com', 80, '/http/${cmd}?${query}');
+ conn.onResponse = (HttpClientResponse response) {
+ StringInputStream stream = new StringInputStream(response.inputStream);
+ StringBuffer data = new StringBuffer();
+ stream.onData = () => data.add(stream.read());
+ stream.onClosed = () {
+ processResponse(data.toString());
+ };
+ };
+}
+
+/// Print the status of a previously sent message.
+void printMessageStatus(String messageId) {
+ QUERY['apimsgid'] = messageId;
+ httpCmd('getmsgcharge', (data) {
+ //TODO String.slice(startIndex, [endIndex])) method and Map.get(k, ifAbsent)
+ // s = MSG_STATUS.get(data.slice(-3), '');
+ // ifAbsent is var not function (belts and braces overkill).
+ var i = data.length - 3;
+ if (i < 0) i = 0;
+ var s = MSG_STATUS[data.substring(i)];
+ if (s == null) s = '';
+ print('$data ($s)');
+ });
+}
+
+// Strip number punctuation and check the number is not obviously illegal.
+String sanitizePhoneNumber(String number) {
+ var result = number.replaceAll(new RegExp(r'[+ ()-]'), '');
+ if (! new RegExp(r'^\d+$').hasMatch(result)) {
+ die('illegal phone number: ${number}');
+ }
+ return result;
+}
+
+// Send text message. The recipient phone number can be a phone number
+// or the name of a phone book entry.
+void sendMessage(String to, String text) {
+ var name;
+ if (CONF['PHONE_BOOK'][to] != null) {
+ name = to;
+ to = CONF['PHONE_BOOK'][to];
+ }
+ to = sanitizePhoneNumber(to);
+ var sender_id = sanitizePhoneNumber(CONF['SENDER_ID']);
+ QUERY['from'] = sender_id;
+ QUERY['to'] = to;
+ QUERY['text'] = text;
+ httpCmd('sendmsg', (result) {
+ var now = new Date.now();
+ if (name != null) to = '$to: $name';
+ OutputStream out = new File.fromPath(LOG_FILE)
+ .openOutputStream(FileMode.APPEND);
+ out.writeString('''
+to: ${to}
+from: ${sender_id}
+date: ${now.toString()}
+result: ${result}
+text: ${text}\n
+''');
+ out.close();
+ print(result);
+ });
+}
+
+void printAccountBalance() {
+ httpCmd('getbalance', (data) => print(data));
+}
+
+void die(String message, [int status=1]) {
+ stderr.writeString(message);
+ exit(status);
+}

0 comments on commit 17237f1

Please sign in to comment.
Something went wrong with that request. Please try again.