1
|
|
|
var request = require('request');
|
2
|
|
|
var express = require('express');
|
3
|
|
|
var FeedParser = require('feedparser');
|
4
|
|
|
var extend = require('extend');
|
5
|
|
|
var ent = require('ent');
|
6
|
|
|
|
7
|
|
|
var USERAGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.75 Safari/537.1";
|
8
|
|
|
|
9
|
|
|
// ------------------------------------------
|
10
|
|
|
// CONSTRUCTOR
|
11
|
|
|
// ------------------------------------------
|
12
|
|
|
|
13
|
|
|
var init = function(){
|
14
|
|
|
info('Starting SARAH ...');
|
15
|
|
|
global.SARAH = SARAH;
|
16
|
|
|
|
17
|
|
|
SARAH.ConfigManager = require('./config.js').init();
|
18
|
|
|
SARAH.PluginManager = require('./plugin.js').init();
|
19
|
|
|
SARAH.LangManager = require('./lang.js').init();
|
20
|
|
|
SARAH.PrivacyManager = require('./privacy.js').init();
|
21
|
|
|
SARAH.PortalManager = require('./portal.js').init();
|
22
|
|
|
SARAH.ScriptManager = require('./script.js').init();
|
23
|
|
|
SARAH.RuleEngine = require('./rules.js').init();
|
24
|
|
|
SARAH.CRONManager = require('./cron.js').init();
|
25
|
|
|
SARAH.ProfileManager = require('./profile.js').init();
|
26
|
|
|
SARAH.Marketplace = require('./marketplace.js').init();
|
27
|
|
|
|
28
|
|
|
/*
|
29
|
|
|
SARAH.PhantomManager = require('./phantom.js').init();
|
30
|
|
|
*/
|
31
|
|
|
|
32
|
|
|
SARAH.run = SARAH.ScriptManager.run;
|
33
|
|
|
SARAH.call = SARAH.ScriptManager.call;
|
34
|
|
|
SARAH.last = SARAH.ScriptManager.last;
|
35
|
|
|
SARAH.find = SARAH.PluginManager.find;
|
36
|
|
|
SARAH.exists = SARAH.PluginManager.exists;
|
37
|
|
|
SARAH.trigger = SARAH.PluginManager.trigger;
|
38
|
|
|
SARAH.listen = SARAH.PluginManager.listen;
|
39
|
|
|
|
40
|
|
|
return SARAH;
|
41
|
|
|
}
|
42
|
|
|
|
43
|
|
|
// ------------------------------------------
|
44
|
|
|
// RSS
|
45
|
|
|
// ------------------------------------------
|
46
|
|
|
|
47
|
|
|
var RSSFeedCache = {};
|
48
|
|
|
var getRSSFeed = function(url, cache){
|
49
|
|
|
|
50
|
|
|
// Use cache
|
51
|
|
|
if (!cache && RSSFeedCache[url]){ return RSSFeedCache[url]; }
|
52
|
|
|
|
53
|
|
|
var feed = { items : [] };
|
54
|
|
|
request(url)
|
55
|
|
|
.pipe(new FeedParser())
|
56
|
|
|
.on('meta', function (meta) { feed.meta = meta; })
|
57
|
|
|
.on('readable', function() {
|
58
|
|
|
var stream = this, item;
|
59
|
|
|
while (item = stream.read()) {
|
60
|
|
|
item.description = ent.decode(item.description);
|
61
|
|
|
feed.items.push(item);
|
62
|
|
|
}
|
63
|
|
|
RSSFeedCache[url] = feed; // Cache
|
64
|
|
|
});
|
|
|
|
|
65
|
|
|
}
|
66
|
|
|
|
67
|
|
|
// ------------------------------------------
|
68
|
|
|
// ASKME
|
69
|
|
|
// ------------------------------------------
|
70
|
|
|
|
71
|
|
|
var ASKME = false;
|
72
|
|
|
var stack = [];
|
73
|
|
|
|
74
|
|
|
var end = function(){
|
75
|
|
|
ASKME = false; next();
|
76
|
|
|
}
|
77
|
|
|
|
78
|
|
|
var next = function(){
|
79
|
|
|
if (stack.length <= 0){
|
80
|
|
|
return SARAH.context('default');
|
81
|
|
|
}
|
82
|
|
|
var args = stack.shift();
|
83
|
|
|
askme(args[0], args[1], args[2], args[3])
|
|
|
|
|
84
|
|
|
}
|
85
|
|
|
|
86
|
|
|
var askme = function(tts, grammar, timeout, callback, wrong){
|
87
|
|
|
if (!grammar) { return; }
|
88
|
|
|
if (!callback){ return; }
|
89
|
|
|
if (ASKME) { return stack.push(arguments); }
|
90
|
|
|
|
91
|
|
|
// Build request
|
92
|
|
|
info('AskMe', ASKME);
|
93
|
|
|
ASKME = { 'sentences':[], 'tags':[] }
|
94
|
|
|
if (tts){
|
95
|
|
|
ASKME.tts = tts;
|
96
|
|
|
ASKME.sync = true;
|
97
|
|
|
ASKME.wrong = wrong;
|
98
|
|
|
}
|
99
|
|
|
for (var g in grammar){
|
|
|
|
|
100
|
|
|
ASKME.sentences.push(g);
|
101
|
|
|
ASKME.tags.push(grammar[g]);
|
102
|
|
|
}
|
103
|
|
|
|
104
|
|
|
// Send request
|
105
|
|
|
remote(ASKME);
|
106
|
|
|
|
107
|
|
|
// Backup
|
108
|
|
|
ASKME.rule = grammar
|
109
|
|
|
ASKME.callback = callback;
|
110
|
|
|
ASKME.token = setTimeout(function(){
|
111
|
|
|
ASKME = false;
|
112
|
|
|
if (timeout <= 0){
|
113
|
|
|
callback(false, end);
|
114
|
|
|
} else {
|
115
|
|
|
SARAH.askme(tts, grammar, 0, callback);
|
116
|
|
|
}
|
117
|
|
|
}, timeout || 16000);
|
|
|
|
|
118
|
|
|
}
|
119
|
|
|
|
120
|
|
|
var answerme = function(req, res, next){ res.end();
|
|
|
|
|
121
|
|
|
if (!ASKME){ return; }
|
122
|
|
|
if (ASKME.token){ clearTimeout(ASKME.token); }
|
123
|
|
|
ASKME.callback(req.param('dictation') || req.param('tag'), end);
|
124
|
|
|
}
|
125
|
|
|
|
126
|
|
|
// ------------------------------------------
|
127
|
|
|
// ROUTER
|
128
|
|
|
// ------------------------------------------
|
129
|
|
|
|
130
|
|
|
var Router = express.Router();
|
131
|
|
|
|
132
|
|
|
Router.all('/sarah/:name', function(req, res, next) {
|
|
|
|
|
133
|
|
|
|
134
|
|
|
var name = req.params.name;
|
135
|
|
|
var options = {};
|
136
|
|
|
extend(true, options, req.query);
|
137
|
|
|
extend(true, options, req.body);
|
138
|
|
|
|
139
|
|
|
// 1. Log action into rule engine
|
140
|
|
|
var entry = SARAH.RuleEngine.log(name, options);
|
141
|
|
|
// info('Rule Engine Log:', entry);
|
142
|
|
|
|
143
|
|
|
// 3. Send back TTS
|
144
|
|
|
var callback = function(data){
|
145
|
|
|
|
146
|
|
|
// Redirect to portlet (no rules, asknect, ...)
|
147
|
|
|
if (req.query.ajax){
|
148
|
|
|
res.redirect('/plugin/'+name);
|
149
|
|
|
return;
|
150
|
|
|
}
|
151
|
|
|
|
152
|
|
|
//res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
153
|
|
|
if (!res.headersSent){
|
154
|
|
|
res.set({'Content-Type': 'text/html; charset=utf-8'});
|
155
|
|
|
}
|
156
|
|
|
|
157
|
|
|
// Speak
|
158
|
|
|
if (data && data.tts){
|
159
|
|
|
var tts = SARAH.ScriptManager.speak(data.tts);
|
160
|
|
|
if (tts){ res.write(tts); }
|
161
|
|
|
}
|
162
|
|
|
|
163
|
|
|
res.status(200).end();
|
164
|
|
|
|
165
|
|
|
// Ask next from data
|
166
|
|
|
if (data && data.asknext){
|
167
|
|
|
if (typeof data.asknext === 'string'){
|
168
|
|
|
SARAH.asknext(data.asknext);
|
169
|
|
|
} else {
|
170
|
|
|
SARAH.asknext(data.asknext.rule, data.asknext);
|
171
|
|
|
}
|
172
|
|
|
}
|
173
|
|
|
else if (options.asknext){
|
174
|
|
|
SARAH.asknext(options.asknext);
|
175
|
|
|
}
|
176
|
|
|
|
177
|
|
|
// 4. Guess next action from RuleEngine
|
178
|
|
|
var next = SARAH.RuleEngine.next(entry);
|
|
|
|
|
179
|
|
|
//info('Rule Engine Guess:', next);
|
180
|
|
|
}
|
181
|
|
|
|
182
|
|
|
// 2. Run plugin's script
|
183
|
|
|
SARAH.run(name, options, callback, true);
|
184
|
|
|
});
|
185
|
|
|
|
186
|
|
|
Router.all('/standby', function(req, res, next) {
|
|
|
|
|
187
|
|
|
var motion = req.query.motion == "True" ? true : false;
|
188
|
|
|
SARAH.ScriptManager.standBy(motion, req.query.client);
|
189
|
|
|
res.end();
|
190
|
|
|
});
|
191
|
|
|
|
192
|
|
|
Router.all('/askme', answerme);
|
193
|
|
|
|
194
|
|
|
|
195
|
|
|
// ------------------------------------------
|
196
|
|
|
// REMOTE
|
197
|
|
|
// ------------------------------------------
|
198
|
|
|
|
199
|
|
|
var _callback = function (err, response, body){
|
200
|
|
|
if (err || response.statusCode != 200) {
|
201
|
|
|
warn("HTTP Error: ", err, response, body);
|
202
|
|
|
return;
|
|
|
|
|
203
|
|
|
}
|
204
|
|
|
};
|
205
|
|
|
|
206
|
|
|
var remote = function(query, callback){
|
207
|
|
|
var url = Config.http.remote;
|
|
|
|
|
208
|
|
|
var querystring = require('querystring');
|
209
|
|
|
url += '?' + querystring.stringify(query);
|
210
|
|
|
|
211
|
|
|
info('Remote: ', url);
|
212
|
|
|
var request = require('request');
|
213
|
|
|
request({ 'url' : url }, callback || _callback);
|
214
|
|
|
};
|
215
|
|
|
|
216
|
|
|
var speak = function(tts, callback){
|
217
|
|
|
|
218
|
|
|
tts = SARAH.ScriptManager.speak(tts, callback);
|
219
|
|
|
if (!tts){ return; }
|
220
|
|
|
|
221
|
|
|
// Hook for TTS
|
222
|
|
|
var qs = {
|
223
|
|
|
'tts' : tts,
|
224
|
|
|
'sync' : callback ? true : false
|
225
|
|
|
};
|
226
|
|
|
return remote(qs, callback);
|
227
|
|
|
}
|
228
|
|
|
|
229
|
|
|
var answer = function(tts, callback){
|
230
|
|
|
var answers = Config.bot.answers.split('|');
|
|
|
|
|
231
|
|
|
var answer = answers[ Math.floor(Math.random() * answers.length)];
|
232
|
|
|
return speak(answer, callback);
|
233
|
|
|
}
|
234
|
|
|
|
235
|
|
|
var shutup = function(once){
|
236
|
|
|
var qs = { 'notts' : 'true' }
|
237
|
|
|
if (once){ qs.once = true; }
|
238
|
|
|
return remote(qs);
|
239
|
|
|
}
|
240
|
|
|
|
241
|
|
|
var play = function(path, callback){
|
242
|
|
|
// Hook for TTS
|
243
|
|
|
var qs = {
|
244
|
|
|
'play' : path,
|
245
|
|
|
'sync' : callback ? true : false
|
246
|
|
|
};
|
247
|
|
|
return remote(qs, callback);
|
248
|
|
|
}
|
249
|
|
|
|
250
|
|
|
var stop = function(path){
|
251
|
|
|
return remote({ 'stop' : path });
|
252
|
|
|
}
|
253
|
|
|
|
254
|
|
|
var runApp = function(path, params){
|
255
|
|
|
var qs = { 'run' : path };
|
256
|
|
|
if (params){ qs.runp = params; }
|
257
|
|
|
return remote(qs);
|
258
|
|
|
}
|
259
|
|
|
|
260
|
|
|
var activate = function(process){
|
261
|
|
|
return remote({ 'activate' : process });
|
262
|
|
|
}
|
263
|
|
|
|
264
|
|
|
var keyText = function(text){
|
265
|
|
|
return remote({ 'keyText' : text });
|
266
|
|
|
}
|
267
|
|
|
|
268
|
|
|
var keyUp = function(key, mod){
|
269
|
|
|
var qs = { 'keyUp' : key };
|
270
|
|
|
if (mod){ qs.keyMod = mod; }
|
271
|
|
|
return remote(qs);
|
272
|
|
|
}
|
273
|
|
|
|
274
|
|
|
var keyDown = function(key, mod){
|
275
|
|
|
var qs = { 'keyDown' : key };
|
276
|
|
|
if (mod){ qs.keyMod = mod; }
|
277
|
|
|
return remote(qs);
|
278
|
|
|
}
|
279
|
|
|
|
280
|
|
|
var keyPress = function(key, mod){
|
281
|
|
|
var qs = { 'keyPress' : key };
|
282
|
|
|
if (mod){ qs.keyMod = mod; }
|
283
|
|
|
return remote(qs);
|
284
|
|
|
}
|
285
|
|
|
|
286
|
|
|
var face = function(pause){
|
287
|
|
|
return remote({ 'face' : pause });
|
288
|
|
|
}
|
289
|
|
|
|
290
|
|
|
var gesture = function(pause){
|
291
|
|
|
return remote({ 'gesture' : pause });
|
292
|
|
|
}
|
293
|
|
|
|
294
|
|
|
var listen = function(pause){
|
295
|
|
|
return remote({ 'listen' : pause });
|
296
|
|
|
}
|
297
|
|
|
|
298
|
|
|
var picture = function(device, path, type){
|
299
|
|
|
var qs = { 'picture' : type || 'true' };
|
300
|
|
|
|
301
|
|
|
if (path){ qs.picture = path; }
|
302
|
|
|
if (device){ qs.device = device; }
|
303
|
|
|
return remote(qs);
|
304
|
|
|
}
|
305
|
|
|
|
306
|
|
|
var recognize = function(path){
|
307
|
|
|
return remote({ 'recognize' : path });
|
308
|
|
|
}
|
309
|
|
|
|
310
|
|
|
var context = function(rules){
|
311
|
|
|
return remote({ 'context' : rules });
|
312
|
|
|
}
|
313
|
|
|
|
314
|
|
|
var grammar = function(rule, xml){
|
315
|
|
|
return remote({ 'grammar' : rule , 'xml' : xml});
|
316
|
|
|
}
|
317
|
|
|
|
318
|
|
|
var asknext = function(rule, options){
|
319
|
|
|
var qs = {'asknext' : rule };
|
320
|
|
|
if (options){ extend(true, qs, options); }
|
321
|
|
|
return remote(qs);
|
322
|
|
|
}
|
323
|
|
|
|
324
|
|
|
// ------------------------------------------
|
325
|
|
|
// PUBLIC
|
326
|
|
|
// ------------------------------------------
|
327
|
|
|
|
328
|
|
|
var SARAH = {
|
329
|
|
|
'init' : init,
|
330
|
|
|
'remote' : remote,
|
331
|
|
|
|
332
|
|
|
'speak' : speak,
|
333
|
|
|
'answer' : answer,
|
334
|
|
|
'shutup' : shutup,
|
335
|
|
|
'play' : play,
|
336
|
|
|
'stop' : stop,
|
337
|
|
|
|
338
|
|
|
'runApp' : runApp,
|
339
|
|
|
'activate' : activate,
|
340
|
|
|
'keyText' : keyText,
|
341
|
|
|
'keyUp' : keyUp,
|
342
|
|
|
'keyDown' : keyDown,
|
343
|
|
|
'keyPress' : keyPress,
|
344
|
|
|
|
345
|
|
|
'face' : face,
|
346
|
|
|
'gesture' : gesture,
|
347
|
|
|
'listen' : listen,
|
348
|
|
|
'picture' : picture,
|
349
|
|
|
'recognize' : recognize,
|
350
|
|
|
|
351
|
|
|
'context' : context,
|
352
|
|
|
'grammar' : grammar,
|
353
|
|
|
'askme' : askme,
|
354
|
|
|
'asknext' : asknext,
|
355
|
|
|
|
356
|
|
|
'getRSSFeed' : getRSSFeed,
|
357
|
|
|
'Router' : Router,
|
358
|
|
|
'USERAGENT' : USERAGENT
|
359
|
|
|
}
|
360
|
|
|
|
361
|
|
|
// Exports SARAH singleton
|
362
|
|
|
exports.init = init; |