chat-platform 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +17 -0
- package/__tests__/chat-platform.js +223 -0
- package/__tests__/context-provider-memory.js +222 -0
- package/__tests__/context-provider-plain-file.js +174 -0
- package/__tests__/context-provider-sqlite.js +239 -0
- package/__tests__/dummy/audio.mp3 +0 -0
- package/__tests__/dummy/file.bin +0 -0
- package/__tests__/dummy/file.mp4 +0 -0
- package/__tests__/dummy/file.pdf +0 -0
- package/__tests__/dummy/image.png +0 -0
- package/__tests__/dummy/mission-control.backup +0 -0
- package/__tests__/dummy/video.mov +0 -0
- package/__tests__/universal-platform.js +190 -0
- package/blank/empty.sqlite +0 -0
- package/chat-context-factory.js +92 -0
- package/chat-log.js +115 -0
- package/chat-platform.js +1276 -0
- package/helpers/lcd.js +120 -0
- package/helpers/promises-queue.js +41 -0
- package/helpers/utils.js +25 -0
- package/index.js +6 -0
- package/jest.config.js +5 -0
- package/lib/lcd.js +151 -0
- package/lib/red-stub.js +212 -0
- package/lib/utils.js +29 -0
- package/model.nlp +688 -0
- package/package.json +32 -0
- package/providers/memory.js +170 -0
- package/providers/plain-file.js +345 -0
- package/providers/sqlite.js +267 -0
- package/universal.js +67 -0
package/chat-platform.js
ADDED
|
@@ -0,0 +1,1276 @@
|
|
|
1
|
+
const _ = require('underscore');
|
|
2
|
+
const _s = require('underscore.string');
|
|
3
|
+
const clc = require('cli-color');
|
|
4
|
+
const prettyjson = require('prettyjson');
|
|
5
|
+
const { isEmpty, when } = require('./lib/utils');
|
|
6
|
+
const EventEmitter = require('events').EventEmitter;
|
|
7
|
+
const inherits = require('util').inherits;
|
|
8
|
+
const lcd = require('./helpers/lcd');
|
|
9
|
+
const Table = require('cli-table');
|
|
10
|
+
const request = require('request').defaults({ encoding: null });
|
|
11
|
+
|
|
12
|
+
const identity = function(obj) { return obj; };
|
|
13
|
+
const green = clc.greenBright;
|
|
14
|
+
const white = clc.white;
|
|
15
|
+
const yellow = clc.yellow;
|
|
16
|
+
const red = clc.red;
|
|
17
|
+
const orange = clc.xterm(214);
|
|
18
|
+
const grey = clc.blackBright;
|
|
19
|
+
|
|
20
|
+
if (global['redbot-chat-platform'] == null) {
|
|
21
|
+
global['redbot-chat-platform'] = {
|
|
22
|
+
messageTypes: [],
|
|
23
|
+
events: [],
|
|
24
|
+
platforms: {},
|
|
25
|
+
params: {}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let _messageTypes = global['redbot-chat-platform'].messageTypes;
|
|
30
|
+
let _events = global['redbot-chat-platform'].events;
|
|
31
|
+
let _platforms = global['redbot-chat-platform'].platforms;
|
|
32
|
+
let _params = global['redbot-chat-platform'].params;
|
|
33
|
+
let _globalCallbacks = {};
|
|
34
|
+
|
|
35
|
+
const ChatExpress = function(options) {
|
|
36
|
+
|
|
37
|
+
const _this = this;
|
|
38
|
+
this.options = _.extend({
|
|
39
|
+
color: null,
|
|
40
|
+
contextProvider: null,
|
|
41
|
+
connector: null,
|
|
42
|
+
inboundMessage: null,
|
|
43
|
+
transport: null,
|
|
44
|
+
transportDescription: null,
|
|
45
|
+
chatIdKey: null,
|
|
46
|
+
userIdKey: null,
|
|
47
|
+
tsKey: null,
|
|
48
|
+
debug: true,
|
|
49
|
+
onStart: null,
|
|
50
|
+
onStop: null,
|
|
51
|
+
RED: null,
|
|
52
|
+
routes: null,
|
|
53
|
+
routesDescription: null,
|
|
54
|
+
events: null,
|
|
55
|
+
relaxChatId: false,
|
|
56
|
+
bundle: false,
|
|
57
|
+
multiWebHook: true
|
|
58
|
+
}, options);
|
|
59
|
+
|
|
60
|
+
this.ins = [];
|
|
61
|
+
this.outs = [];
|
|
62
|
+
this.uses = [];
|
|
63
|
+
|
|
64
|
+
// init platforms
|
|
65
|
+
_platforms[this.options.transport] = {
|
|
66
|
+
id: this.options.transport,
|
|
67
|
+
name: this.options.transportDescription,
|
|
68
|
+
universal: false,
|
|
69
|
+
color: this.options.color != null ? this.options.color : '#bbbbbb'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// configuration warnings
|
|
73
|
+
if (_.isEmpty(this.options.chatIdKey) && !_.isFunction(this.options.chatIdKey)) {
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.log(yellow('WARNING: chatIdKey option is empty'));
|
|
76
|
+
}
|
|
77
|
+
if (_.isEmpty(this.options.userIdKey) && !_.isFunction(this.options.userIdKey)) {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.log(yellow('WARNING: userIdKey option is empty'));
|
|
80
|
+
}
|
|
81
|
+
if (_.isEmpty(this.options.transport)) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.log(yellow('WARNING: transport option is empty'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function evaluateParam(payload, newKey, optionKey, chatServer) {
|
|
87
|
+
var options = _this.options;
|
|
88
|
+
if (options[optionKey] != null) {
|
|
89
|
+
if (_.isString(options[optionKey]) && newKey != options[optionKey]) {
|
|
90
|
+
payload[newKey] = payload[options[optionKey]];
|
|
91
|
+
delete payload[options[optionKey]];
|
|
92
|
+
} else if (_.isFunction(options[optionKey])) {
|
|
93
|
+
payload[newKey] = options[optionKey].call(chatServer, payload);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseMessage(payload, options, chatServer) {
|
|
99
|
+
var instanceOptions = chatServer.getOptions();
|
|
100
|
+
|
|
101
|
+
payload = _.clone(payload);
|
|
102
|
+
// sets inbound
|
|
103
|
+
payload.inbound = true;
|
|
104
|
+
// sets the transport
|
|
105
|
+
if (!_.isEmpty(_this.options.transport)) {
|
|
106
|
+
payload.transport = _this.options.transport;
|
|
107
|
+
}if (!_.isEmpty(instanceOptions.transport)) {
|
|
108
|
+
// use the new registered transport platform if any
|
|
109
|
+
payload.transport = instanceOptions.transport;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
evaluateParam(payload, 'chatId', 'chatIdKey', chatServer);
|
|
113
|
+
evaluateParam(payload, 'userId', 'userIdKey', chatServer);
|
|
114
|
+
evaluateParam(payload, 'ts', 'tsKey', chatServer);
|
|
115
|
+
evaluateParam(payload, 'type', 'type', chatServer);
|
|
116
|
+
evaluateParam(payload, 'language', 'language', chatServer);
|
|
117
|
+
// default userId on chatId, never leave it blank, pay attention
|
|
118
|
+
// on removing this constraint (some chat context may be lost)
|
|
119
|
+
payload.userId = !_.isEmpty(payload.userId) ? payload.userId : payload.chatId;
|
|
120
|
+
// evaluate callbacks
|
|
121
|
+
var callbacks = chatServer.getCallbacks();
|
|
122
|
+
_(['chatId', 'userId', 'ts', 'type', 'language', 'messageId']).each(function(callbackName) {
|
|
123
|
+
if (_.isFunction(callbacks[callbackName])) {
|
|
124
|
+
payload[callbackName] = callbacks[callbackName].call(chatServer, payload)
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// at this point should have at least the values chatId and type
|
|
128
|
+
if (payload.chatId == null && !options.relaxChatId) {
|
|
129
|
+
throw 'Error: inbound message key "chatId" for transport ' + _this.options.transport + ' is empty\n\n'
|
|
130
|
+
+ 'See here: https://github.com/guidone/node-red-contrib-chatbot/wiki/Universal-Connector-node for an '
|
|
131
|
+
+ 'explanation about this error.';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return payload;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function warningInboundMiddleware(message) {
|
|
138
|
+
// check if message is null, perhaps someone forgot to resolve a promise
|
|
139
|
+
if (message == null) {
|
|
140
|
+
// eslint-disable-next-line no-console
|
|
141
|
+
console.log(yellow('WARNING: a middleware is returning an empty message'));
|
|
142
|
+
}
|
|
143
|
+
if (message.payload == null) {
|
|
144
|
+
// eslint-disable-next-line no-console
|
|
145
|
+
console.log(yellow('WARNING: a middleware is returning an empty payload in message'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function prepareForConsole(payload) {
|
|
150
|
+
var result = _.clone(payload) || {};
|
|
151
|
+
if (result.content instanceof Buffer) {
|
|
152
|
+
result.content = '<Buffer>';
|
|
153
|
+
}
|
|
154
|
+
if (result.ts != null) {
|
|
155
|
+
result.ts = result.ts.toString();
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// eslint-disable-next-line max-params
|
|
161
|
+
async function createMessage(chatId, userId, messageId, inboudMessage, chatServer) {
|
|
162
|
+
const options = chatServer.getOptions();
|
|
163
|
+
const contextProvider = options.contextProvider;
|
|
164
|
+
const onCreateMessage = _.isFunction(options.onCreateMessage) ? options.onCreateMessage : identity;
|
|
165
|
+
inboudMessage = inboudMessage || {};
|
|
166
|
+
|
|
167
|
+
const chatContext = await when(contextProvider.getOrCreate(chatId, userId, {
|
|
168
|
+
chatId: chatId,
|
|
169
|
+
userId: userId,
|
|
170
|
+
transport: options.transport,
|
|
171
|
+
}));
|
|
172
|
+
await chatContext.set({
|
|
173
|
+
authorized: false,
|
|
174
|
+
pending: false,
|
|
175
|
+
language: null
|
|
176
|
+
});
|
|
177
|
+
const message = _.extend({}, inboudMessage, {
|
|
178
|
+
originalMessage: {
|
|
179
|
+
chatId: chatId,
|
|
180
|
+
userId: userId,
|
|
181
|
+
messageId: messageId,
|
|
182
|
+
transport: options.transport,
|
|
183
|
+
language: null
|
|
184
|
+
},
|
|
185
|
+
chat() {
|
|
186
|
+
return contextProvider.get(
|
|
187
|
+
chatId,
|
|
188
|
+
userId,
|
|
189
|
+
{
|
|
190
|
+
userId: this.originalMessage.userId,
|
|
191
|
+
transport: this.originalMessage.transport,
|
|
192
|
+
chatId: this.originalMessage.chatId
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
api() {
|
|
197
|
+
return chatServer;
|
|
198
|
+
},
|
|
199
|
+
isTransportAvailable(transport, message) {
|
|
200
|
+
return chatServer.isTransportAvailable(userId, transport, message);
|
|
201
|
+
},
|
|
202
|
+
isTransportPreferred(transport, message) {
|
|
203
|
+
return chatServer.isTransportPreferred(userId, transport, message);
|
|
204
|
+
},
|
|
205
|
+
client() {
|
|
206
|
+
return options.connector;
|
|
207
|
+
},
|
|
208
|
+
get(value) {
|
|
209
|
+
return this.originalMessage[value];
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return onCreateMessage.call(chatServer, message);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function inboundMessage(payload, chatServer) {
|
|
216
|
+
var contextProvider = chatServer.getOptions().contextProvider;
|
|
217
|
+
if (chatServer.isDebug()) {
|
|
218
|
+
// eslint-disable-next-line no-console
|
|
219
|
+
console.log(orange('-- INBOUND MESSAGE --'));
|
|
220
|
+
try {
|
|
221
|
+
// eslint-disable-next-line no-console
|
|
222
|
+
console.log(prettyjson.render(payload));
|
|
223
|
+
} catch (e) {
|
|
224
|
+
// eslint-disable-next-line no-console
|
|
225
|
+
console.log('PrettyJSON error');
|
|
226
|
+
}
|
|
227
|
+
// eslint-disable-next-line no-console
|
|
228
|
+
console.log('');
|
|
229
|
+
}
|
|
230
|
+
// parse the message to extract the minimum payload needed for chat-platform to work properly
|
|
231
|
+
// could raise errors, relay to chat server
|
|
232
|
+
try {
|
|
233
|
+
var parsedMessage = parseMessage(payload, _this.options, chatServer);
|
|
234
|
+
} catch(e) {
|
|
235
|
+
chatServer.emit('error', e);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// create the node red message structure
|
|
239
|
+
const message = {
|
|
240
|
+
originalMessage: _.extend({}, payload, {
|
|
241
|
+
chatId: parsedMessage.chatId,
|
|
242
|
+
userId: parsedMessage.userId,
|
|
243
|
+
messageId: parsedMessage.messageId,
|
|
244
|
+
transport: parsedMessage.transport,
|
|
245
|
+
language: parsedMessage.language,
|
|
246
|
+
ts: parsedMessage.ts
|
|
247
|
+
}),
|
|
248
|
+
payload: {
|
|
249
|
+
type: parsedMessage.type,
|
|
250
|
+
chatId: parsedMessage.chatId,
|
|
251
|
+
userId: parsedMessage.userId,
|
|
252
|
+
ts: parsedMessage.ts,
|
|
253
|
+
transport: parsedMessage.transport,
|
|
254
|
+
inbound: true
|
|
255
|
+
},
|
|
256
|
+
chat: function() {
|
|
257
|
+
return contextProvider.get(
|
|
258
|
+
parsedMessage.chatId,
|
|
259
|
+
parsedMessage.userId,
|
|
260
|
+
{ userId: this.originalMessage.userId, transport: this.originalMessage.transport, chatId: this.originalMessage.chatId }
|
|
261
|
+
);
|
|
262
|
+
},
|
|
263
|
+
api: function() {
|
|
264
|
+
return chatServer;
|
|
265
|
+
},
|
|
266
|
+
client: function() {
|
|
267
|
+
return chatServer.getOptions().connector;
|
|
268
|
+
},
|
|
269
|
+
get(value) {
|
|
270
|
+
return this.originalMessage[value];
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
// create empty promise
|
|
274
|
+
let stack = new Promise(function(resolve) {
|
|
275
|
+
resolve(message);
|
|
276
|
+
});
|
|
277
|
+
// if any context provider, then create the context
|
|
278
|
+
if (contextProvider != null) {
|
|
279
|
+
stack = stack
|
|
280
|
+
.then(() => when(contextProvider.getOrCreate(
|
|
281
|
+
parsedMessage.chatId,
|
|
282
|
+
parsedMessage.userId,
|
|
283
|
+
{ userId: parsedMessage.userId, transport: parsedMessage.transport, chatId: parsedMessage.chatId }
|
|
284
|
+
)))
|
|
285
|
+
.then(chatContext => when(chatContext.set({ language: parsedMessage.language, authorized: false, pending: false })))
|
|
286
|
+
.then(function() {
|
|
287
|
+
return when(message);
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
// eslint-disable-next-line no-console
|
|
291
|
+
console.log(yellow('WARNING: context provider was not specified'));
|
|
292
|
+
}
|
|
293
|
+
// run general middleware
|
|
294
|
+
_(_this.uses.concat(chatServer.getUseMiddleWares())).each(function(filter) {
|
|
295
|
+
stack = stack.then(function(message) {
|
|
296
|
+
// encapsulate the promise to catch the error with the source code of the middleware
|
|
297
|
+
return new Promise(function(resolve, reject) {
|
|
298
|
+
warningInboundMiddleware(message);
|
|
299
|
+
when(filter.call(chatServer, message))
|
|
300
|
+
.then(resolve)
|
|
301
|
+
.catch(function (error) {
|
|
302
|
+
error.sourceCode = filter.toString();
|
|
303
|
+
reject(error);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
// run ins middleware without any specific type
|
|
309
|
+
_(_this.ins.concat(chatServer.getInMiddleWares())).each(function(filter) {
|
|
310
|
+
stack = stack.then(function(message) {
|
|
311
|
+
// encapsulate the promise to catch the error with the source code of the middleware
|
|
312
|
+
return new Promise(function(resolve, reject) {
|
|
313
|
+
warningInboundMiddleware(message);
|
|
314
|
+
// if message type is null
|
|
315
|
+
if (filter.type == null) {
|
|
316
|
+
when(filter.method.call(chatServer, message))
|
|
317
|
+
.then(resolve)
|
|
318
|
+
.catch(function(error) {
|
|
319
|
+
error.sourceCode = filter.method.toString();
|
|
320
|
+
reject(error);
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
resolve(message)
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
// run ins middleware without a specific type
|
|
329
|
+
_(_this.ins).each(function(filter) {
|
|
330
|
+
stack = stack.then(function(message) {
|
|
331
|
+
// encapsulate the promise to catch the error with the source code of the middleware
|
|
332
|
+
return new Promise(function (resolve, reject) {
|
|
333
|
+
warningInboundMiddleware(message);
|
|
334
|
+
// if message type is the same
|
|
335
|
+
if (filter.type === message.payload.type || filter.type === '*') {
|
|
336
|
+
when(filter.method.call(chatServer, message))
|
|
337
|
+
.then(resolve)
|
|
338
|
+
.catch(function(error) {
|
|
339
|
+
error.sourceCode = filter.method.toString();
|
|
340
|
+
reject(error);
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
resolve(message)
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
// finally
|
|
349
|
+
stack
|
|
350
|
+
.then(function(message) {
|
|
351
|
+
if (message.payload != null && message.payload.type != null) {
|
|
352
|
+
if (chatServer.isDebug()) {
|
|
353
|
+
// eslint-disable-next-line no-console
|
|
354
|
+
console.log(orange('-- RELAY MESSAGE --'));
|
|
355
|
+
try {
|
|
356
|
+
// eslint-disable-next-line no-console
|
|
357
|
+
console.log(prettyjson.render(prepareForConsole(message.payload)));
|
|
358
|
+
} catch(e) {
|
|
359
|
+
// eslint-disable-next-line no-console
|
|
360
|
+
console.log('Unable to render');
|
|
361
|
+
}
|
|
362
|
+
// eslint-disable-next-line no-console
|
|
363
|
+
console.log('');
|
|
364
|
+
}
|
|
365
|
+
chatServer.emit('message', message);
|
|
366
|
+
} else {
|
|
367
|
+
// do nothing
|
|
368
|
+
if (chatServer.isDebug()) {
|
|
369
|
+
// eslint-disable-next-line no-console
|
|
370
|
+
console.log(orange('-- DISCARDED MESSAGE (not handled by middlewares) --'));
|
|
371
|
+
// eslint-disable-next-line no-console
|
|
372
|
+
console.log('');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
})
|
|
377
|
+
.catch(function(error) {
|
|
378
|
+
lcd.dump(error, 'Error in chat-platform.js');
|
|
379
|
+
// dump source code if present
|
|
380
|
+
if (error != null && !_.isEmpty(error.sourceCode)) {
|
|
381
|
+
// eslint-disable-next-line no-console
|
|
382
|
+
console.log(lcd.red(error.sourceCode));
|
|
383
|
+
// eslint-disable-next-line no-console
|
|
384
|
+
console.log('');
|
|
385
|
+
}
|
|
386
|
+
if (chatServer != null) {
|
|
387
|
+
chatServer.emit('error', error);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function warningOutboundMiddleware(message) {
|
|
393
|
+
if (message == null) {
|
|
394
|
+
// eslint-disable-next-line no-console
|
|
395
|
+
console.log(yellow('WARNING: a middleware is returning an empty value'));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function outboundMessage(message, chatServer) {
|
|
400
|
+
// if simulator message, then skip, no matter what is the platform
|
|
401
|
+
if (message.originalMessage != null && message.originalMessage.simulator === true) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const instanceOptions = chatServer.getOptions();
|
|
406
|
+
// check if the message is from the right platform (in static class or instance)
|
|
407
|
+
/*if (message.originalMessage != null && message.originalMessage.transport !== _this.options.transport &&
|
|
408
|
+
message.originalMessage.transport !== instanceOptions.transport) {
|
|
409
|
+
// exit, it's not from the current platform
|
|
410
|
+
if (chatServer.isDebug()) {
|
|
411
|
+
// eslint-disable-next-line no-console
|
|
412
|
+
console.log(yellow('Skipped incoming message for platform: ' + message.originalMessage.transport));
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}*/
|
|
416
|
+
|
|
417
|
+
if (chatServer.isDebug()) {
|
|
418
|
+
// eslint-disable-next-line no-console
|
|
419
|
+
console.log(orange('-- OUTBOUND MESSAGE --'));
|
|
420
|
+
// eslint-disable-next-line no-console
|
|
421
|
+
console.log(prettyjson.render(prepareForConsole(message.payload)));
|
|
422
|
+
// eslint-disable-next-line no-console
|
|
423
|
+
console.log('');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// create empty promise
|
|
427
|
+
let stack = new Promise(function(resolve) {
|
|
428
|
+
resolve(message);
|
|
429
|
+
});
|
|
430
|
+
// check for chatId, if not present check for userId-chatId translator, otherwise fail
|
|
431
|
+
stack = stack.then(message => {
|
|
432
|
+
const { transport } = instanceOptions;
|
|
433
|
+
if (!isEmpty(message.payload.chatId)) {
|
|
434
|
+
// if there's a chatId, everything ok, skip
|
|
435
|
+
return message;
|
|
436
|
+
} else if (_.isFunction(_globalCallbacks.getChatIdFromUserId) && message.originalMessage.userId != null) {
|
|
437
|
+
// try to call the callback
|
|
438
|
+
try {
|
|
439
|
+
return when(_globalCallbacks.getChatIdFromUserId.call(chatServer, message.originalMessage.userId, transport, message))
|
|
440
|
+
.then(
|
|
441
|
+
chatId => {
|
|
442
|
+
if (isEmpty(chatId)) {
|
|
443
|
+
// eslint-disable-next-line no-console
|
|
444
|
+
console.log(lcd.error(`[onGetChatIdFromUserId] The userId<->chatId resolver was not able to find a valid chatId for user ${message.originalMessage.userId}(${transport})`));
|
|
445
|
+
throw new Error(`The userId<->chatId resolver was not able to find a valid chatId for user ${message.originalMessage.userId}(${transport})`);
|
|
446
|
+
} else {
|
|
447
|
+
if (instanceOptions.debug) {
|
|
448
|
+
// eslint-disable-next-line no-console
|
|
449
|
+
console.log(lcd.green('[onGetChatIdFromUserId]') + lcd.grey(` Resolved ${message.originalMessage.userId}(${transport}) in chatId:${chatId}`));
|
|
450
|
+
}
|
|
451
|
+
message.payload.chatId = chatId;
|
|
452
|
+
return message;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
} catch(e) {
|
|
457
|
+
// eslint-disable-next-line no-console
|
|
458
|
+
console.log(lcd.error(`[onGetChatIdFromUserId] runtime error in chatId<->userId resolver for user ${message.originalMessage.userId}(${transport})`));
|
|
459
|
+
lcd.dump(e);
|
|
460
|
+
throw new Error(`[onGetChatIdFromUserId] runtime error in chatId<->userId resolver for user ${message.originalMessage.userId}(${transport})`);
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
// provide some feedback for not being able to resolve a chatId
|
|
464
|
+
if (instanceOptions.debug) {
|
|
465
|
+
if (_.isFunction(_globalCallbacks.getChatIdFromUserId) && isEmpty(message.originalMessage.userId)) {
|
|
466
|
+
// eslint-disable-next-line no-console
|
|
467
|
+
console.log(lcd.warn(`[onGetChatIdFromUserId] Callback was provided but incoming message has no userId (${transport}), unable to resolve a valid chatId`));
|
|
468
|
+
} else if (!_.isFunction(_globalCallbacks.getChatIdFromUserId)) {
|
|
469
|
+
// eslint-disable-next-line no-console
|
|
470
|
+
console.log(lcd.warn('[onGetChatIdFromUserId] Callback was NOT provided and chatId is empty'));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// raise error only if not relaxChatId
|
|
474
|
+
if (!instanceOptions.relaxChatId) {
|
|
475
|
+
throw new Error('Incoming message has empty chatId and no chatId - userId resolved has been provided');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return message;
|
|
479
|
+
});
|
|
480
|
+
// run general middleware
|
|
481
|
+
_(_this.uses.concat(chatServer.getUseMiddleWares())).each(function(filter) {
|
|
482
|
+
stack = stack.then(function(message) {
|
|
483
|
+
return new Promise(function(resolve, reject) {
|
|
484
|
+
warningOutboundMiddleware(message);
|
|
485
|
+
when(filter.call(chatServer, message))
|
|
486
|
+
.then(resolve)
|
|
487
|
+
.catch(function (error) {
|
|
488
|
+
error.sourceCode = filter.toString();
|
|
489
|
+
reject(error);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
// run outs middleware without a specific typs
|
|
495
|
+
_(_this.outs.concat(chatServer.getOutMiddleWares())).each(function(filter) {
|
|
496
|
+
stack = stack.then(function(message) {
|
|
497
|
+
return new Promise(function(resolve, reject) {
|
|
498
|
+
// check if message is null, perhaps someone forgot to resolve a promise
|
|
499
|
+
warningOutboundMiddleware(message);
|
|
500
|
+
// if message type is the same
|
|
501
|
+
if (filter.type == null) {
|
|
502
|
+
when(filter.method.call(chatServer, message))
|
|
503
|
+
.then(resolve)
|
|
504
|
+
.catch(function (error) {
|
|
505
|
+
error.sourceCode = filter.toString();
|
|
506
|
+
reject(error);
|
|
507
|
+
});
|
|
508
|
+
} else {
|
|
509
|
+
resolve(message);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
// run outs middleware with a specific typs
|
|
515
|
+
_(_this.outs.concat(chatServer.getOutMiddleWares())).each(function(filter) {
|
|
516
|
+
stack = stack.then(function(message) {
|
|
517
|
+
return new Promise(function(resolve, reject) {
|
|
518
|
+
// check if message is null, perhaps someone forgot to resolve a promise
|
|
519
|
+
warningOutboundMiddleware(message);
|
|
520
|
+
// if message type is the same
|
|
521
|
+
if (message.payload != null && filter.type === message.payload.type) {
|
|
522
|
+
when(filter.method.call(chatServer, message))
|
|
523
|
+
.then(resolve)
|
|
524
|
+
.catch(function (error) {
|
|
525
|
+
error.sourceCode = filter.toString();
|
|
526
|
+
reject(error);
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
resolve(message);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
// finally
|
|
535
|
+
return stack
|
|
536
|
+
.catch(function(error) {
|
|
537
|
+
// eslint-disable-next-line no-console
|
|
538
|
+
console.log(red(error));
|
|
539
|
+
if (chatServer != null) {
|
|
540
|
+
chatServer.emit('error', error);
|
|
541
|
+
}
|
|
542
|
+
// rethrow error so it can be caught by the sender node
|
|
543
|
+
throw error;
|
|
544
|
+
})
|
|
545
|
+
.then(function(message) {
|
|
546
|
+
return message;
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function unmountEvents(events, chatServer) {
|
|
551
|
+
var options = chatServer.getOptions();
|
|
552
|
+
var connector = options.connector;
|
|
553
|
+
if (connector != null) {
|
|
554
|
+
if (options.inboundMessageEvent != null) {
|
|
555
|
+
connector.off(options.inboundMessageEvent);
|
|
556
|
+
}
|
|
557
|
+
_(events).each(function (callback, eventName) {
|
|
558
|
+
connector.off(eventName);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function mountEvents(events, chatServer) {
|
|
564
|
+
var connector = chatServer.getOptions().connector;
|
|
565
|
+
if (connector != null && _.isFunction(connector.on)) {
|
|
566
|
+
_(events).each(function (callback, eventName) {
|
|
567
|
+
connector.on(eventName, callback.bind(chatServer));
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return when(true);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function unmountRoutes(RED, routes, chatServer) {
|
|
574
|
+
if (routes != null) {
|
|
575
|
+
const endpoints = _(routes).keys();
|
|
576
|
+
if (!_.isEmpty(endpoints)) {
|
|
577
|
+
let routesCount = RED.httpNode._router.stack.length;
|
|
578
|
+
let idx = 0;
|
|
579
|
+
let stack = RED.httpNode._router.stack;
|
|
580
|
+
for(; idx < stack.length;) {
|
|
581
|
+
const route = stack[idx];
|
|
582
|
+
if (route != null && route.name != null) {
|
|
583
|
+
const routeName = String(route.name).replace('bound ', '');
|
|
584
|
+
if (_.contains(endpoints, routeName)) {
|
|
585
|
+
stack.splice(idx, 1);
|
|
586
|
+
} else {
|
|
587
|
+
idx += 1;
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
idx += 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (RED.httpNode._router.stack.length >= routesCount) {
|
|
594
|
+
// eslint-disable-next-line no-console
|
|
595
|
+
chatServer.warning(`Improperly removed some routes from Express. This is normal when multiple bots are running in the same server.`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function generateCallback(route, chatServer) {
|
|
602
|
+
const options = chatServer.getOptions();
|
|
603
|
+
const { webHookScheme, multiWebHook } = options;
|
|
604
|
+
// build specific url if needed
|
|
605
|
+
let specificUrl = '';
|
|
606
|
+
if (multiWebHook && _.isFunction(webHookScheme)) {
|
|
607
|
+
specificUrl = webHookScheme.call(chatServer);
|
|
608
|
+
}
|
|
609
|
+
return `${route}${!_.isEmpty(specificUrl) ? `/${specificUrl}` : ''}`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// eslint-disable-next-line max-params
|
|
613
|
+
function mountRoutes(RED, routes, routesDescription, chatServer) {
|
|
614
|
+
if (routes != null && RED == null) {
|
|
615
|
+
chatServer.warn('"RED" param is empty, impossible to mount the routes');
|
|
616
|
+
}
|
|
617
|
+
if (routes != null && RED != null) {
|
|
618
|
+
const uiPort = RED.settings.get('uiPort');
|
|
619
|
+
const options = chatServer.getOptions();
|
|
620
|
+
// eslint-disable-next-line no-console
|
|
621
|
+
console.log('');
|
|
622
|
+
// eslint-disable-next-line no-console
|
|
623
|
+
console.log(grey('------ WebHooks for ' + options.transport.toUpperCase() + '----------------'));
|
|
624
|
+
_(routes).map((middleware, route) => {
|
|
625
|
+
const host = 'http://localhost' + (uiPort != '80' ? ':' + uiPort : '');
|
|
626
|
+
const callback = generateCallback(route, chatServer);
|
|
627
|
+
// make description
|
|
628
|
+
let description = null;
|
|
629
|
+
if (routesDescription != null && _.isString(routesDescription[route])) {
|
|
630
|
+
description = routesDescription[route];
|
|
631
|
+
} else if (routesDescription != null && _.isFunction(routesDescription[route])) {
|
|
632
|
+
description = routesDescription[route].call(chatServer);
|
|
633
|
+
}
|
|
634
|
+
// eslint-disable-next-line no-console
|
|
635
|
+
console.log(green(host + callback) + (description != null ? grey(' - ') + white(description) : ''));
|
|
636
|
+
// attach to Express instance
|
|
637
|
+
const escaped = String(`^${callback}$`).replace(RegExp('/', 'g'), '\\/');
|
|
638
|
+
RED.httpNode.use(new RegExp(escaped), middleware.bind(chatServer));
|
|
639
|
+
return null;
|
|
640
|
+
});
|
|
641
|
+
// eslint-disable-next-line no-console
|
|
642
|
+
console.log('');
|
|
643
|
+
}
|
|
644
|
+
return when(true);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
var methods = {
|
|
648
|
+
|
|
649
|
+
'in': function() {
|
|
650
|
+
var type = null;
|
|
651
|
+
var method = null;
|
|
652
|
+
if (arguments.length === 1) {
|
|
653
|
+
method = arguments[0];
|
|
654
|
+
} else if (arguments.length === 2) {
|
|
655
|
+
type = arguments[0];
|
|
656
|
+
method = arguments[1];
|
|
657
|
+
} else {
|
|
658
|
+
throw '.in() wrong number of parameters';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
_this.ins.push({
|
|
662
|
+
type: type,
|
|
663
|
+
method: method
|
|
664
|
+
});
|
|
665
|
+
return methods;
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
out: function() {
|
|
669
|
+
var type = null;
|
|
670
|
+
var method = null;
|
|
671
|
+
if (arguments.length === 1) {
|
|
672
|
+
method = arguments[0];
|
|
673
|
+
} else if (arguments.length === 2) {
|
|
674
|
+
type = arguments[0];
|
|
675
|
+
method = arguments[1];
|
|
676
|
+
} else {
|
|
677
|
+
throw '.out() wrong number of parameters';
|
|
678
|
+
}
|
|
679
|
+
_this.outs.push({
|
|
680
|
+
type: type,
|
|
681
|
+
method: method
|
|
682
|
+
});
|
|
683
|
+
return methods;
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
use: function(method) {
|
|
687
|
+
_this.uses.push(method);
|
|
688
|
+
return methods;
|
|
689
|
+
},
|
|
690
|
+
|
|
691
|
+
mixin: function(obj) {
|
|
692
|
+
_this._mixins = _.extend(_this._mixin || {}, obj);
|
|
693
|
+
return methods;
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
registerMessageType: function(type, name, description, validator) {
|
|
697
|
+
if (type == null || typeof type !== 'string') {
|
|
698
|
+
throw 'Missing type in .registerMessageType()';
|
|
699
|
+
}
|
|
700
|
+
name = name != null ? name : _s.capitalize(type);
|
|
701
|
+
let typeDescriptor = _(_messageTypes).findWhere({ type: type });
|
|
702
|
+
if (typeDescriptor == null) {
|
|
703
|
+
typeDescriptor = { type: type };
|
|
704
|
+
_messageTypes.push(typeDescriptor);
|
|
705
|
+
}
|
|
706
|
+
if (name != null) {
|
|
707
|
+
typeDescriptor.name = name;
|
|
708
|
+
}
|
|
709
|
+
if (description != null) {
|
|
710
|
+
typeDescriptor.description = description;
|
|
711
|
+
}
|
|
712
|
+
if (typeDescriptor.platforms == null) {
|
|
713
|
+
typeDescriptor.platforms = {};
|
|
714
|
+
}
|
|
715
|
+
if (typeDescriptor.validators == null) {
|
|
716
|
+
typeDescriptor.validators = {};
|
|
717
|
+
}
|
|
718
|
+
typeDescriptor.platforms[options.transport] = true;
|
|
719
|
+
if (_.isFunction(validator)) {
|
|
720
|
+
typeDescriptor.validators[options.transport] = validator;
|
|
721
|
+
}
|
|
722
|
+
return this;
|
|
723
|
+
},
|
|
724
|
+
registerEvent: function(name, description) {
|
|
725
|
+
if (name == null || typeof name !== 'string') {
|
|
726
|
+
throw 'Missing name in .registerEvent()';
|
|
727
|
+
}
|
|
728
|
+
var eventDescriptor = _(_events).findWhere({ name: name });
|
|
729
|
+
if (eventDescriptor == null) {
|
|
730
|
+
eventDescriptor = { name: name };
|
|
731
|
+
_events.push(eventDescriptor);
|
|
732
|
+
}
|
|
733
|
+
eventDescriptor.name = name;
|
|
734
|
+
if (description != null) {
|
|
735
|
+
eventDescriptor.description = description;
|
|
736
|
+
}
|
|
737
|
+
if (eventDescriptor.platforms == null) {
|
|
738
|
+
eventDescriptor.platforms = {};
|
|
739
|
+
}
|
|
740
|
+
eventDescriptor.platforms[options.transport] = true;
|
|
741
|
+
return this;
|
|
742
|
+
},
|
|
743
|
+
registerParam: function(name, type, config = {}) {
|
|
744
|
+
if (name == null || typeof name !== 'string') {
|
|
745
|
+
throw 'Missing name in .registerParam()';
|
|
746
|
+
}
|
|
747
|
+
if (type == null || typeof type !== 'string') {
|
|
748
|
+
throw 'Missing type in .registerParam()';
|
|
749
|
+
}
|
|
750
|
+
if (_params[options.transport] == null) {
|
|
751
|
+
_params[options.transport] = [];
|
|
752
|
+
}
|
|
753
|
+
_params[options.transport].push({
|
|
754
|
+
name,
|
|
755
|
+
type,
|
|
756
|
+
placeholder: config.placeholder,
|
|
757
|
+
label: !_.isEmpty(config.label) ? config.label : name,
|
|
758
|
+
description: config.description,
|
|
759
|
+
default: config.default,
|
|
760
|
+
options: config.options
|
|
761
|
+
});
|
|
762
|
+
return this;
|
|
763
|
+
},
|
|
764
|
+
|
|
765
|
+
createServer: function(options) {
|
|
766
|
+
|
|
767
|
+
options = _.extend({}, _this.options, options);
|
|
768
|
+
var chatServer = null;
|
|
769
|
+
var _ins = [];
|
|
770
|
+
var _uses = [];
|
|
771
|
+
var _outs = [];
|
|
772
|
+
var _callbacks = {};
|
|
773
|
+
|
|
774
|
+
var ChatServer = function(options) {
|
|
775
|
+
this.options = options;
|
|
776
|
+
this.warn = function(msg) {
|
|
777
|
+
var text = '[' + options.transport.toUpperCase() + '] ' + msg;
|
|
778
|
+
// eslint-disable-next-line no-console
|
|
779
|
+
console.log(yellow(text));
|
|
780
|
+
this.emit('warning', text);
|
|
781
|
+
};
|
|
782
|
+
this.error = function(msg) {
|
|
783
|
+
var text = '[' + options.transport.toUpperCase() + '] ' + msg;
|
|
784
|
+
// eslint-disable-next-line no-console
|
|
785
|
+
console.log(red(text));
|
|
786
|
+
this.emit('error', text);
|
|
787
|
+
};
|
|
788
|
+
this.warning = function(msg) {
|
|
789
|
+
var text = '[' + options.transport.toUpperCase() + '] ' + msg;
|
|
790
|
+
// eslint-disable-next-line no-console
|
|
791
|
+
console.log(red(text));
|
|
792
|
+
this.emit('warning', text);
|
|
793
|
+
};
|
|
794
|
+
this.log = function(obj) {
|
|
795
|
+
// eslint-disable-next-line no-console
|
|
796
|
+
console.log(prettyjson.render(obj));
|
|
797
|
+
};
|
|
798
|
+
this.request = function(options = {}) {
|
|
799
|
+
return new Promise(function(resolve, reject) {
|
|
800
|
+
request(options, function(error, response, body) {
|
|
801
|
+
if (error) {
|
|
802
|
+
reject(`Error calling URL ${options.url}`);
|
|
803
|
+
} else {
|
|
804
|
+
resolve(body);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
};
|
|
809
|
+
this.getOptions = function() {
|
|
810
|
+
return this.options;
|
|
811
|
+
};
|
|
812
|
+
this.isDebug = function() {
|
|
813
|
+
return this.options != null && this.options.debug;
|
|
814
|
+
};
|
|
815
|
+
this.getConnector = function() {
|
|
816
|
+
return this.options.connector;
|
|
817
|
+
};
|
|
818
|
+
this.send = function(message) {
|
|
819
|
+
var _this = this;
|
|
820
|
+
// If more than one message is enqued in the same payload, send one by one through the middlewares if there
|
|
821
|
+
// is not bundle option. Bundle option is used in case of multimodal messages (for example audio and video
|
|
822
|
+
// at the same time) that need to be sent with a unique call
|
|
823
|
+
if (options.bundle || !_.isArray(message.payload)) {
|
|
824
|
+
return outboundMessage(message, this);
|
|
825
|
+
} else {
|
|
826
|
+
var task = when(true);
|
|
827
|
+
_(message.payload).each(function(payload) {
|
|
828
|
+
task = task.then(function() {
|
|
829
|
+
return outboundMessage(_.extend({}, message, { payload: payload }), _this);
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
return task;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
this.receive = function(message) {
|
|
836
|
+
inboundMessage(message, this);
|
|
837
|
+
};
|
|
838
|
+
// eslint-disable-next-line max-params
|
|
839
|
+
this.createMessage = function(chatId, userId, messageId, inboudMessage) {
|
|
840
|
+
return createMessage(chatId, userId, messageId, inboudMessage, this);
|
|
841
|
+
};
|
|
842
|
+
this.in = function() {
|
|
843
|
+
var type = null;
|
|
844
|
+
var method = null;
|
|
845
|
+
if (arguments.length === 1) {
|
|
846
|
+
method = arguments[0];
|
|
847
|
+
} else if (arguments.length === 2) {
|
|
848
|
+
type = arguments[0];
|
|
849
|
+
method = arguments[1];
|
|
850
|
+
} else {
|
|
851
|
+
throw '.in() wrong number of parameters';
|
|
852
|
+
}
|
|
853
|
+
_ins.push({
|
|
854
|
+
type: type,
|
|
855
|
+
method: method
|
|
856
|
+
});
|
|
857
|
+
return methods;
|
|
858
|
+
};
|
|
859
|
+
this.getInMiddleWares = function() {
|
|
860
|
+
return _ins;
|
|
861
|
+
};
|
|
862
|
+
this.use = function(method) {
|
|
863
|
+
_uses.push(method);
|
|
864
|
+
return methods;
|
|
865
|
+
};
|
|
866
|
+
this.getUseMiddleWares = function() {
|
|
867
|
+
return _uses;
|
|
868
|
+
};
|
|
869
|
+
this.registerMessageType = function(type, name, description, validator) {
|
|
870
|
+
if (type == null || typeof type !== 'string') {
|
|
871
|
+
throw 'Missing type in .registerMessageType()';
|
|
872
|
+
}
|
|
873
|
+
name = name != null ? name : _s.capitalize(type);
|
|
874
|
+
let typeDescriptor = _(_messageTypes).findWhere({ type: type });
|
|
875
|
+
if (typeDescriptor == null) {
|
|
876
|
+
typeDescriptor = { type: type };
|
|
877
|
+
_messageTypes.push(typeDescriptor);
|
|
878
|
+
}
|
|
879
|
+
if (name != null) {
|
|
880
|
+
typeDescriptor.name = name;
|
|
881
|
+
}
|
|
882
|
+
if (description != null) {
|
|
883
|
+
typeDescriptor.description = description;
|
|
884
|
+
}
|
|
885
|
+
if (typeDescriptor.platforms == null) {
|
|
886
|
+
typeDescriptor.platforms = {};
|
|
887
|
+
}
|
|
888
|
+
if (typeDescriptor.validators == null) {
|
|
889
|
+
typeDescriptor.validators = {};
|
|
890
|
+
}
|
|
891
|
+
typeDescriptor.platforms[options.transport] = true;
|
|
892
|
+
if (_.isFunction(validator)) {
|
|
893
|
+
typeDescriptor.validators[options.transport] = validator;
|
|
894
|
+
}
|
|
895
|
+
return this;
|
|
896
|
+
};
|
|
897
|
+
this.registerEvent = function(name, description) {
|
|
898
|
+
if (name == null || typeof name !== 'string') {
|
|
899
|
+
throw 'Missing name in .registerEvent()';
|
|
900
|
+
}
|
|
901
|
+
var eventDescriptor = _(_events).findWhere({ name: name });
|
|
902
|
+
if (eventDescriptor == null) {
|
|
903
|
+
eventDescriptor = { name: name };
|
|
904
|
+
_events.push(eventDescriptor);
|
|
905
|
+
}
|
|
906
|
+
eventDescriptor.name = name;
|
|
907
|
+
if (description != null) {
|
|
908
|
+
eventDescriptor.description = description;
|
|
909
|
+
}
|
|
910
|
+
if (eventDescriptor.platforms == null) {
|
|
911
|
+
eventDescriptor.platforms = {};
|
|
912
|
+
}
|
|
913
|
+
eventDescriptor.platforms[options.transport] = true;
|
|
914
|
+
return this;
|
|
915
|
+
};
|
|
916
|
+
this.registerParam = function(name, type, config = {}) {
|
|
917
|
+
if (name == null || typeof name !== 'string') {
|
|
918
|
+
throw 'Missing name in .registerParam()';
|
|
919
|
+
}
|
|
920
|
+
if (type == null || typeof type !== 'string') {
|
|
921
|
+
throw 'Missing type in .registerParam()';
|
|
922
|
+
}
|
|
923
|
+
if (_params[options.transport] == null) {
|
|
924
|
+
_params[options.transport] = [];
|
|
925
|
+
}
|
|
926
|
+
_params[options.transport].push({
|
|
927
|
+
name,
|
|
928
|
+
type,
|
|
929
|
+
placeholder: config.placeholder,
|
|
930
|
+
label: !_.isEmpty(config.label) ? config.label : name,
|
|
931
|
+
description: config.description,
|
|
932
|
+
default: config.default,
|
|
933
|
+
options: config.options
|
|
934
|
+
});
|
|
935
|
+
return this;
|
|
936
|
+
};
|
|
937
|
+
this.registerPlatform = function(name, label) {
|
|
938
|
+
_platforms[name] = {
|
|
939
|
+
id: name,
|
|
940
|
+
name: !_.isEmpty(label) ? label : name,
|
|
941
|
+
universal: true
|
|
942
|
+
};
|
|
943
|
+
var options = this.getOptions();
|
|
944
|
+
options.transport = name;
|
|
945
|
+
options.transportDescription = !_.isEmpty(label) ? label : name;
|
|
946
|
+
return this;
|
|
947
|
+
};
|
|
948
|
+
this.out = function() {
|
|
949
|
+
var type = null;
|
|
950
|
+
var method = null;
|
|
951
|
+
if (arguments.length === 1) {
|
|
952
|
+
method = arguments[0];
|
|
953
|
+
} else if (arguments.length === 2) {
|
|
954
|
+
type = arguments[0];
|
|
955
|
+
method = arguments[1];
|
|
956
|
+
// automatically register the type
|
|
957
|
+
this.registerMessageType(type);
|
|
958
|
+
} else {
|
|
959
|
+
throw '.out() wrong number of parameters';
|
|
960
|
+
}
|
|
961
|
+
_outs.push({
|
|
962
|
+
type: type,
|
|
963
|
+
method: method
|
|
964
|
+
});
|
|
965
|
+
return methods;
|
|
966
|
+
};
|
|
967
|
+
this.getOutMiddleWares = function() {
|
|
968
|
+
return _outs;
|
|
969
|
+
};
|
|
970
|
+
this.start = function() {
|
|
971
|
+
var _this = this;
|
|
972
|
+
var stack = when(true);
|
|
973
|
+
var options = this.getOptions();
|
|
974
|
+
if (_.isFunction(options.onStart)) {
|
|
975
|
+
// execute on start callback, ensure it's a properly chained promise
|
|
976
|
+
stack = stack.then(function() {
|
|
977
|
+
return when(options.onStart.call(chatServer));
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
return stack
|
|
981
|
+
.then(function() {
|
|
982
|
+
return mountRoutes(options.RED, options.routes, options.routesDescription, _this);
|
|
983
|
+
})
|
|
984
|
+
.then(function() {
|
|
985
|
+
return mountEvents(options.events, _this);
|
|
986
|
+
})
|
|
987
|
+
.then(function() {
|
|
988
|
+
return _.isFunction(options.onStarted) ? when(options.onStarted.call(_this)) : when(true);
|
|
989
|
+
})
|
|
990
|
+
.then(function() {
|
|
991
|
+
if (_this.isDebug()) {
|
|
992
|
+
// eslint-disable-next-line no-console
|
|
993
|
+
console.log(green('Chat server started, transport: ') + white(options.transport));
|
|
994
|
+
}
|
|
995
|
+
// listen to inbound event
|
|
996
|
+
var connector = options.connector;
|
|
997
|
+
if (connector != null && options.inboundMessageEvent != null) {
|
|
998
|
+
connector.on(options.inboundMessageEvent, function (message) {
|
|
999
|
+
inboundMessage(message, chatServer);
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
_this.emit('start');
|
|
1003
|
+
},
|
|
1004
|
+
function(error) {
|
|
1005
|
+
// eslint-disable-next-line no-console
|
|
1006
|
+
_this.error(error);
|
|
1007
|
+
});
|
|
1008
|
+
};
|
|
1009
|
+
this.onChatId = function(callback) {
|
|
1010
|
+
_callbacks.chatId = callback;
|
|
1011
|
+
};
|
|
1012
|
+
this.onUserId = function(callback) {
|
|
1013
|
+
_callbacks.userId = callback;
|
|
1014
|
+
};
|
|
1015
|
+
this.onTimestamp = function(callback) {
|
|
1016
|
+
_callbacks.ts = callback;
|
|
1017
|
+
};
|
|
1018
|
+
this.onLanguage = function(callback) {
|
|
1019
|
+
_callbacks.language = callback;
|
|
1020
|
+
};
|
|
1021
|
+
this.onMessageId = function(callback) {
|
|
1022
|
+
_callbacks.messageId = callback;
|
|
1023
|
+
};
|
|
1024
|
+
this.onGetChatIdFromUserId = function(callback) {
|
|
1025
|
+
_globalCallbacks.getChatIdFromUserId = callback;
|
|
1026
|
+
};
|
|
1027
|
+
this.onGetPreferredTransport = function(callback) {
|
|
1028
|
+
_globalCallbacks.onGetPreferredTransport = callback;
|
|
1029
|
+
};
|
|
1030
|
+
this.isTransportAvailable = function(userId, transport, message) {
|
|
1031
|
+
if (!_.isFunction(_globalCallbacks.getChatIdFromUserId)) {
|
|
1032
|
+
throw new Error('Resolver chatId<->userId not defined');
|
|
1033
|
+
}
|
|
1034
|
+
try {
|
|
1035
|
+
return when(_globalCallbacks.getChatIdFromUserId.call(chatServer, userId, transport, message))
|
|
1036
|
+
.then(chatId => {
|
|
1037
|
+
return chatId != null
|
|
1038
|
+
});
|
|
1039
|
+
} catch(e) {
|
|
1040
|
+
// todo better error displaying
|
|
1041
|
+
// eslint-disable-next-line no-console
|
|
1042
|
+
console.log('Error in resolver chatId<->userId', e);
|
|
1043
|
+
throw new Error('Error in resolver chatId<->userId', e);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
this.isTransportPreferred = function(userId, message) {
|
|
1047
|
+
if (!_.isFunction(_globalCallbacks.onGetPreferredTransport)) {
|
|
1048
|
+
throw new Error('Resolver userId<->preferred transport not defined');
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
return when(_globalCallbacks.onGetPreferredTransport.call(chatServer, userId, message))
|
|
1052
|
+
.then(preferredTransport => {
|
|
1053
|
+
return preferredTransport === this.options.transport;
|
|
1054
|
+
});
|
|
1055
|
+
} catch(e) {
|
|
1056
|
+
// todo better error displaying
|
|
1057
|
+
// eslint-disable-next-line no-console
|
|
1058
|
+
console.log('Error in resolver chatId<->preferred transport', e);
|
|
1059
|
+
throw new Error('Error in resolver chatId<->preferred transport', e);
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
this.onGetUserIdFromChatId = function(callback) {
|
|
1063
|
+
_callbacks.getUserIdFromChatId = callback;
|
|
1064
|
+
};
|
|
1065
|
+
this.getCallbacks = function() {
|
|
1066
|
+
return _callbacks;
|
|
1067
|
+
};
|
|
1068
|
+
this.stop = function() {
|
|
1069
|
+
this.emit('stop');
|
|
1070
|
+
var options = this.getOptions();
|
|
1071
|
+
unmountRoutes(options.RED, options.routes, this);
|
|
1072
|
+
unmountEvents(options.events, this);
|
|
1073
|
+
var stack = when(true);
|
|
1074
|
+
if (_.isFunction(options.onStop)) {
|
|
1075
|
+
stack = stack.then(function() {
|
|
1076
|
+
return when(options.onStop.call(chatServer));
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
return stack;
|
|
1080
|
+
};
|
|
1081
|
+
EventEmitter.call(this);
|
|
1082
|
+
};
|
|
1083
|
+
inherits(ChatServer, EventEmitter);
|
|
1084
|
+
_.extend(ChatServer.prototype, _this._mixins);
|
|
1085
|
+
// create chat instance
|
|
1086
|
+
chatServer = new ChatServer(options);
|
|
1087
|
+
return chatServer;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
};
|
|
1091
|
+
return methods;
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
/*
|
|
1095
|
+
Static methods for ChatExpress
|
|
1096
|
+
*/
|
|
1097
|
+
ChatExpress.getMessageTypes = function() {
|
|
1098
|
+
return _(_messageTypes).map(function(item) {
|
|
1099
|
+
return {
|
|
1100
|
+
value: item.type,
|
|
1101
|
+
label: item.name,
|
|
1102
|
+
platforms: _(item.platforms).keys()
|
|
1103
|
+
};
|
|
1104
|
+
});
|
|
1105
|
+
};
|
|
1106
|
+
ChatExpress.getEvents = function() {
|
|
1107
|
+
return _(_events).map(function(item) {
|
|
1108
|
+
return {
|
|
1109
|
+
value: item.name,
|
|
1110
|
+
label: item.description,
|
|
1111
|
+
platforms: _(item.platforms).keys()
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
};
|
|
1115
|
+
ChatExpress.getParams = function() {
|
|
1116
|
+
return _params;
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
function compatibilityTable(items, options) {
|
|
1120
|
+
options = _.extend({ column: 'Column' }, options);
|
|
1121
|
+
// collect platforms except universal
|
|
1122
|
+
var platforms = _(ChatExpress.getPlatforms()).chain()
|
|
1123
|
+
.map(function(platform) {
|
|
1124
|
+
return platform.id;
|
|
1125
|
+
})
|
|
1126
|
+
.reject(function(id) {
|
|
1127
|
+
return id === 'universal';
|
|
1128
|
+
})
|
|
1129
|
+
.sort()
|
|
1130
|
+
.value();
|
|
1131
|
+
// build header
|
|
1132
|
+
var head = [options.column];
|
|
1133
|
+
_(platforms).each(function(name) {
|
|
1134
|
+
head.push(_s.capitalize(name));
|
|
1135
|
+
});
|
|
1136
|
+
var colAligns = ['left'];
|
|
1137
|
+
_.times(platforms.length, function() {
|
|
1138
|
+
colAligns.push('middle');
|
|
1139
|
+
});
|
|
1140
|
+
// create table
|
|
1141
|
+
var table = new Table({
|
|
1142
|
+
head: head,
|
|
1143
|
+
colAligns: colAligns,
|
|
1144
|
+
style: {
|
|
1145
|
+
head: ['green']
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
// message types columns
|
|
1149
|
+
_(items).chain()
|
|
1150
|
+
.sortBy(function(type) {
|
|
1151
|
+
return type.name;
|
|
1152
|
+
})
|
|
1153
|
+
.each(function(type) {
|
|
1154
|
+
var row = [type.name];
|
|
1155
|
+
_(platforms).each(function(platform) {
|
|
1156
|
+
if (type.platforms[platform]) {
|
|
1157
|
+
row.push('✔');
|
|
1158
|
+
} else {
|
|
1159
|
+
row.push('');
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
table.push(row);
|
|
1163
|
+
});
|
|
1164
|
+
// eslint-disable-next-line no-console
|
|
1165
|
+
console.log(table.toString());
|
|
1166
|
+
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* @method showCompatibilityChart
|
|
1171
|
+
* Print out a compatibility char for message types
|
|
1172
|
+
*/
|
|
1173
|
+
ChatExpress.showCompatibilityChart = function() {
|
|
1174
|
+
compatibilityTable(_messageTypes, { column: 'Message type' });
|
|
1175
|
+
compatibilityTable(_events, { column: 'Event name' });
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* @method getPlatforms
|
|
1180
|
+
* Return an array of available platforms
|
|
1181
|
+
* @return {Array}
|
|
1182
|
+
*/
|
|
1183
|
+
ChatExpress.getPlatforms = function() {
|
|
1184
|
+
var platforms = _(_platforms).keys();
|
|
1185
|
+
|
|
1186
|
+
return _(platforms).chain()
|
|
1187
|
+
.map(function(platform) {
|
|
1188
|
+
return {
|
|
1189
|
+
id: _platforms[platform].id,
|
|
1190
|
+
name: _platforms[platform].name,
|
|
1191
|
+
universal: _platforms[platform].universal,
|
|
1192
|
+
color: _platforms[platform].color
|
|
1193
|
+
};
|
|
1194
|
+
})
|
|
1195
|
+
.sortBy(function(platform) {
|
|
1196
|
+
return platform.name != null ? platform.name : platform.id;
|
|
1197
|
+
})
|
|
1198
|
+
.value();
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* @method isSupported
|
|
1203
|
+
* Check if a platform is supported (or a message type for a specified platform)
|
|
1204
|
+
* @param {String} platform Platform id (telegram, facebook, ...)
|
|
1205
|
+
* @param {String} type Message type (image, document, ...)
|
|
1206
|
+
* @return {Boolean}
|
|
1207
|
+
*/
|
|
1208
|
+
ChatExpress.isSupported = function(platform, type) {
|
|
1209
|
+
if (type == null) {
|
|
1210
|
+
const platforms = ChatExpress.getPlatforms();
|
|
1211
|
+
return _(platforms).findWhere({ id: platform }) != null;
|
|
1212
|
+
} else {
|
|
1213
|
+
const messageType = _(_messageTypes).findWhere({ type: type });
|
|
1214
|
+
return messageType != null && messageType.platforms[platform];
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* @method isValidFile
|
|
1220
|
+
* Check if a file type is valid (extension, size, etc), if nothing is specified file is assumed to be valid
|
|
1221
|
+
* @param {String} platform Platform id (telegram, facebook, ...)
|
|
1222
|
+
* @param {String} type Message type (image, document, ...)
|
|
1223
|
+
* @param {Object} file The file descriptor
|
|
1224
|
+
* @param {String} file.filename The filename of the file
|
|
1225
|
+
* @param {Buffer} file.buffer The buffer
|
|
1226
|
+
* @param {String} file.extension The extension of the file (with leading dot)
|
|
1227
|
+
* @param {String} file.mimeType Mime type of the file
|
|
1228
|
+
* @return {String} Null if no errors
|
|
1229
|
+
*/
|
|
1230
|
+
ChatExpress.isValidFile = function(platform, type, file) {
|
|
1231
|
+
const messageType = _(_messageTypes).findWhere({ type: type });
|
|
1232
|
+
if (messageType != null) {
|
|
1233
|
+
const validator = messageType.validators[platform];
|
|
1234
|
+
if (validator != null) {
|
|
1235
|
+
return validator(file);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return null;
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* @method registerParam
|
|
1243
|
+
* Register param for all transport/platforms
|
|
1244
|
+
* @param {String} name
|
|
1245
|
+
* @param {String} type Type of param
|
|
1246
|
+
* @param {Object} config Params of the param, pun intended
|
|
1247
|
+
* @chainable
|
|
1248
|
+
*/
|
|
1249
|
+
ChatExpress.registerParam = function(name, type, config = {}) {
|
|
1250
|
+
if (name == null || typeof name !== 'string') {
|
|
1251
|
+
throw 'Missing name in .registerParam()';
|
|
1252
|
+
}
|
|
1253
|
+
if (type == null || typeof type !== 'string') {
|
|
1254
|
+
throw 'Missing type in .registerParam()';
|
|
1255
|
+
}
|
|
1256
|
+
if (_params.all == null) {
|
|
1257
|
+
_params.all = [];
|
|
1258
|
+
}
|
|
1259
|
+
_params.all.push({
|
|
1260
|
+
name,
|
|
1261
|
+
type,
|
|
1262
|
+
placeholder: config.placeholder,
|
|
1263
|
+
label: !_.isEmpty(config.label) ? config.label : name,
|
|
1264
|
+
description: config.description,
|
|
1265
|
+
default: config.default,
|
|
1266
|
+
options: config.options
|
|
1267
|
+
});
|
|
1268
|
+
return this;
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
ChatExpress.reset = function() {
|
|
1272
|
+
// reset global callbacks, will be re-registered with deploy
|
|
1273
|
+
_globalCallbacks = {};
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
module.exports = ChatExpress;
|