shopping-list-sms-service
Version:
An SMS service for a shopping assistance.
401 lines (356 loc) • 10.8 kB
JavaScript
var argv = require('optimist').argv;
var sys = require('util');
var fs = require('fs');
var _ = require('underscore');
var Backbone = require('backbone');
var smsd = require('sms');
//var smsd = require('sms');
var dirty = require('dirty');
var verboseMode = argv.v || false;
var runDir = argv.d || process.cwd();
var db = dirty(runDir + '/list.db');
var db2 = dirty(runDir + '/hashes.db');
var watchFilename = 'message.txt';
// DEMO TASKS
if (argv.demo) {
var task = detectTask('Kaufe am Do 3x Butter bei Aldi Hohe Str');
console.log(task);
var task = detectTask('Einkauf am Do bei Aldi Hohe Str?');
console.log(task);
var task = detectTask('Geschäft Aldi Hohe Str?');
console.log(task);
var task = detectTask('Butter gekauft');
console.log(task);
var task = detectTask('Schema');
console.log(task);
var task = detectTask('Memo Das ist der eigentliche Notiztext');
console.log(task);
}
var Item = Backbone.Model.extend({
defaults: {
due: null,
quantity: 1,
product: '',
trader: '',
store: '',
origin: ''
}
});
var Items = Backbone.Collection.extend({
model: Item
});
var list = new Items();
var hashes = [];
var phoneNumber = null;
if (argv.cmd || argv.try) {
loadData(function dataLoaded() {
phoneNumber = argv.to;
var isDataChanged = executeTask( detectTask(argv.cmd || argv.try) );
if (isDataChanged) {
saveData();
}
});
} else {
initialize();
}
function initialize () {
loadData(function dataLoaded() {
readTasks(function(tasks){
var isDataChanged = false;
// if (verboseMode) {
// console.log(tasks);
// }
for (var i=0; i<tasks.length; i++) {
isDataChanged = isDataChanged || executeTask(tasks[i]);
if (i === tasks.length-1 && isDataChanged) {
saveData();
}
}
});
});
}
function readTasks (callback) {
var tasks = [];
//smsd.fetchMessagesFromGateway(function filterMessages(storedMessages) { // fetches directly from gateway
smsd.readMessagesFromDb(function filterMessages(storedMessages) { // reads from data source
var newMessageDetected = false;
storedMessages.forEach(function (message) {
if (hashes && hashes.length>0 && _.indexOf(hashes, message.get('hash'))>-1) {
// ignore message
if (verboseMode) {
console.log("ignore " + message.get('hash'));
}
} else {
newMessageDetected = true;
hashes.push(message.get('hash'));
var task = detectTask(message.get('message'));
if (!_.isEmpty(task)) {
phoneNumber = message.get('phoneNumber');
tasks.push(task);
}
}
});
if (newMessageDetected) {
saveHashes();
}
callback(tasks);
});
}
function zeroFill (val) {
val = new String(val);
if (val.length === 1) val = '0' + val;
return val;
}
function logToFile (filename, text, next) {
var d = new Date();
var date = d.getFullYear() + '-' + zeroFill(d.getMonth()+1) + '-' + zeroFill(d.getDate()) + ' ' + zeroFill(d.getHours()) + ':' + zeroFill(d.getMinutes()) + ':' + zeroFill(d.getSeconds());
fs.appendFile(filename, date + "\t" + text + "\n", 'utf8', next);
}
function logTask (task) {
logToFile('smsshopping.log', task, function(err){
//if (err) throw err;
});
}
function logUnknown (message) {
logToFile('unknown.log', message, function(err){
//if (err) throw err;
});
}
function logMemo (memo) {
logToFile('memo.log', memo, function(err){
//if (err) throw err;
});
}
function executeTask (task) {
var isDataChanged = false;
if (verboseMode || argv.try) {
console.log("execute: " + task.origin);
//console.log(task);
}
if (task.command != 'memo') {
logTask(task.origin);
}
switch (task.command) {
case 'add':
isDataChanged = true;
var product = {
due: task.due,
quantity: task.quantity || 1,
product: task.product,
trader: task.trader || '',
store: task.store || '',
origin: task.origin
};
list.add(product);
if (task.forceReply) {
reply(new Backbone.Collection(product), ' wurde hinzugefügt.');
}
break;
case 'ls':
var filter = {};
var matchGiven = [];
if (task.due) filter.due = task.due;
if (task.trader) {
// add trader filter
filter.trader = task.trader;
}
if (task.store) filter.store = task.store;
if (_.isEmpty(filter)) {
matchGiven = list;
} else {
matchGiven = list.where(filter);
}
if (task.trader) {
filter.trader = '';
var matchAny = list.where(filter);
matchGiven = _.union(matchGiven, matchAny);
}
// console.log("match...");
// console.log(JSON.stringify(match));
reply(matchGiven, ' ist einzukaufen.');
break;
case 'rm':
var filter = {};
var matchGiven = [];
if (task.product) filter.product = task.product;
matchGiven = list.where(filter);
// console.log("match...");
// console.log(JSON.stringify(matchGiven));
remove(matchGiven, task.forceReply);
break;
case 'man':
submitReply(phoneNumber, 'Setzen und abfragen: (Kaufe|Einkauf)( am Do)( 3x)( Butter)( bei Aldi)( Hohe Str)(?) / Löschen: Butter gekauft');
break;
case 'memo':
logMemo(phoneNumber+"\t"+task.origin.replace(/^memo /i,''));
break;
}
return isDataChanged;
}
function reply (collection, appendix) {
var messages = [];
collection.forEach(function (model){
messages.push(model.get('product'));
});
var text = messages.join(', ');
submitReply(phoneNumber, text + appendix);
}
function remove (collection, forceReply) {
var messages = [];
collection.forEach(function (model){
messages.push(model.get('product'));
});
var text = messages.join(', ') || 'Nichts';
list.remove(collection);
saveData();
if (forceReply) {
submitReply(phoneNumber, text + ' wurde entfernt.');
}
}
function submitReply (to, message) {
if (argv.try) {
console.log("reply to " + (to || '???') + ": " + message);
console.log("Message not sent in TRY mode!");
} else if (verboseMode) {
console.log("reply to " + (to || '???') + ": " + message);
}
if (!argv.try && to && message) {
// this is a workaround for bug in optimist, please give phone numbers by a leading plus sign including country code
var to = new String(to);
to = (to.indexOf('+') !== 0) ? '+' + to : to;
smsd.sendMessage({
to: to,
message: message,
success: function(response) {
//console.log(response);
}
});
}
}
function loadData (callback) {
db.on('load', function() {
list = new Items(db.get('list') || []);
hashes = db2.get('hashes') || [];
callback();
});
// callback();
}
function removeMessages (callback) {
smsd.removeMessagesFromGateway(callback);
}
function saveHashes () {
db2.set("hashes", hashes, function hashesSaved (){
});
}
function saveData () {
db.set("list", list, function listSaved (){
writeDataToFile(watchFilename, list);
if (verboseMode) {
console.log("list saved..");
// console.log(list);
}
});
}
function detectTask (message) {
var tasks = {
'add': 'kaufe( am [a-z]{0,2}){0,1}( [0-9]{1,3}x){0,1}( [a-zäöüß]{3,})( bei [a-zäöüß]{3,}){0,1}( [a-zäöüß ]{3,}){0,1}([!]{0,1})',
'ls': 'einkauf( am [a-z]{0,2}){0,1}( bei [a-zäöüß]{3,}){0,1}( [a-zäöüß ]{3,}){0,1}\\?',
'rm': '([a-zäöüß]{3,}) gekauft([!]{0,1})',
'man': 'sche(ma)',
'memo': 'memo (.+)',
'info': 'geschäft( [a-z]{3,}){0,1}( [a-z ]{3,}){0,1}\\?'
};
var task = {};
message = utf8(message);
for (var t in tasks) {
var taskreg = new RegExp(tasks[t],'i');
var matcher = message.match(taskreg);
if (matcher) {
// fix undefined parts to an empty string
for (var i=0; i<matcher.length; i++) {
matcher[i] = matcher[i] || ''
}
//if (verboseMode) {
// console.log(matcher);
//}
if (matcher.length>1) {
switch (t) {
case 'add':
task = {
command: t,
due: matcher[1].replace(/ am /,''),
quantity: matcher[2].replace(/ /g,'').replace(/x/,'') || 1,
product: matcher[3].replace(/ /g,''),
trader: matcher[4].replace(/ bei /g,''),
store: matcher[5].replace(/^ /g,''),
forceReply: (matcher[6].replace(/^ /g,'')) ? 1 : 0,
origin: message
};
if (!task.product || task.product === 'bei') {
throw Error('Product required');
}
break;
case 'ls':
task = {
command: t,
due: matcher[1].replace(/ am /,''),
trader: matcher[2].replace(/ bei /g,''),
store: matcher[3].replace(/^ /g,''),
forceReply: 1,
origin: message
};
break;
case 'rm':
task = {
command: t,
product: matcher[1].replace(/ /g,''),
forceReply: (matcher[2].replace(/^ /g,'')) ? 1 : 0,
origin: message
};
break;
case 'info':
task = {
command: t,
trader: matcher[1].replace(/ /g,''),
store: matcher[2].replace(/^ /g,''),
forceReply: 1,
origin: message
};
break;
case 'man':
task = {
command: t,
forceReply: 1,
origin: message
};
break;
case 'memo':
task = {
command: t,
forceReply: 0,
origin: message
};
break;
}
if (!task.command) {
throw Error('Task required');
}
return task;
} else {
logUnknown(tasks[t]);
}
}
}
return task;
}
function utf8 (txt) {
// http://www.developershome.com/sms/gsmAlphabet.asp
// http://spin.atomicobject.com/2011/09/08/converting-utf-8-to-the-7-bit-gsm-default-alphabet/
return new Buffer(txt).toString('utf8');
}
function writeDataToFile (filename, list) {
var products = list.pluck("product");
fs.writeFile(filename, products.join("\n"), 'utf8', function(err){
if (err) throw err;
});
}