chat-platform 2.1.4 → 3.1.0

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "globals": {
3
- "Promise": true
3
+ "Promise": true,
4
+ "fetch": true,
5
+ "URLSearchParams": true
4
6
  },
5
7
  "env": {
6
8
  "node": true,
@@ -1,4 +1,3 @@
1
- const moment = require('moment');
2
1
  const assert = require('chai').assert;
3
2
  const RED = require('../lib/red-stub')();
4
3
  const ChatExpress = require('../chat-platform');
@@ -79,7 +78,7 @@ describe('Chat platform framework', () => {
79
78
  chatIdKey: payload => payload.myChatIdKey,
80
79
  userIdKey: payload => payload.myUserIdKey,
81
80
  type: payload => payload.type,
82
- tsKey: () => moment(),
81
+ tsKey: () => new Date(),
83
82
  transport: 'generic',
84
83
  inboundMessageEvent: 'message',
85
84
  connector: connector,
@@ -103,7 +102,8 @@ describe('Chat platform framework', () => {
103
102
  assert.equal(message.payload.userId, '62');
104
103
  assert.equal(message.payload.inbound, true);
105
104
  assert.equal(message.payload.transport, 'generic');
106
- assert.equal(message.payload.ts.isValid(), true);
105
+ assert.instanceOf(message.payload.ts, Date);
106
+ assert.isFalse(isNaN(message.payload.ts.getTime()));
107
107
  assert.isFunction(message.chat);
108
108
  const variables = message.chat().all();
109
109
  assert.equal(variables.pending, false);
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
  const assert = require('chai').assert;
3
3
  const fs = require('fs');
4
4
  const os = require('os');
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
  const assert = require('chai').assert;
3
3
  const fs = require('fs');
4
4
  const os = require('os');
@@ -1,4 +1,3 @@
1
- const moment = require('moment');
2
1
  const chai = require('chai');
3
2
  const assert = chai.assert;
4
3
  const expect = chai.expect;
@@ -114,7 +113,7 @@ describe('Universal Connector', () => {
114
113
  assert.equal(variables.payload, 'Bazinga');
115
114
  assert.equal(variables.authorized, false);
116
115
  assert.equal(variables.language, 'it');
117
- assert.instanceOf(message.payload.ts, moment);
116
+ assert.instanceOf(message.payload.ts, Date);
118
117
  done();
119
118
  });
120
119
  chatServer.receive({
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
 
3
3
  const contextProviders = {};
4
4
  contextProviders.memory = require('./providers/memory');
@@ -59,7 +59,7 @@ const ContextProviders = function(RED) {
59
59
  },
60
60
 
61
61
  hasProvider(provider) {
62
- return _(methods.getProviders()).contains(provider);
62
+ return _.includes(methods.getProviders(), provider);
63
63
  },
64
64
 
65
65
  getProvider(providerName, params = {}) {
package/chat-log.js CHANGED
@@ -1,5 +1,4 @@
1
- var _ = require('underscore');
2
- var moment = require('moment');
1
+ var _ = require('lodash');
3
2
  var fs = require('fs');
4
3
  var lcd = require('./helpers/lcd');
5
4
 
@@ -103,7 +102,7 @@ module.exports = function(variables) {
103
102
  + (!_.isEmpty(name) ? '[' + name.join(' ') + '] ' : '')
104
103
  + (inbound ? '> ' : '< ')
105
104
  + (transport != null ? '[' + transport.toUpperCase() + '] ' : '')
106
- + moment().toString() + ' - ' + stringifiedMessage;
105
+ + new Date().toString() + ' - ' + stringifiedMessage;
107
106
  }
108
107
  }
109
108
 
package/chat-platform.js CHANGED
@@ -1,5 +1,5 @@
1
- const _ = require('underscore');
2
- const _s = require('underscore.string');
1
+ const _ = require('lodash');
2
+ const ws = require('ws');
3
3
  const clc = require('cli-color');
4
4
  const prettyjson = require('prettyjson');
5
5
  const { when } = require('./lib/utils');
@@ -7,7 +7,6 @@ const EventEmitter = require('events').EventEmitter;
7
7
  const inherits = require('util').inherits;
8
8
  const lcd = require('./helpers/lcd');
9
9
  const Table = require('cli-table');
10
- const request = require('request').defaults({ encoding: null });
11
10
 
12
11
  const identity = function(obj) { return obj; };
13
12
  const green = clc.greenBright;
@@ -25,11 +24,20 @@ if (global['redbot-chat-platform'] == null) {
25
24
  params: {}
26
25
  };
27
26
  }
27
+ // WebSocket endpoints are mounted on the raw http server, which is shared by every chatbot of the
28
+ // instance: the registry has to live in the global space like the others, and it has to survive
29
+ // ChatExpress.reset() since the "upgrade" listener attached to the server is never removed
30
+ if (global['redbot-chat-platform'].wsRoutes == null) {
31
+ global['redbot-chat-platform'].wsRoutes = {};
32
+ global['redbot-chat-platform'].wsServers = new WeakSet();
33
+ }
28
34
 
29
35
  let _messageTypes = global['redbot-chat-platform'].messageTypes;
30
36
  let _events = global['redbot-chat-platform'].events;
31
37
  let _platforms = global['redbot-chat-platform'].platforms;
32
38
  let _params = global['redbot-chat-platform'].params;
39
+ let _wsRoutes = global['redbot-chat-platform'].wsRoutes;
40
+ let _wsServers = global['redbot-chat-platform'].wsServers;
33
41
  let _globalCallbacks = {};
34
42
 
35
43
  const ChatExpress = function(options) {
@@ -52,6 +60,9 @@ const ChatExpress = function(options) {
52
60
  RED: null,
53
61
  routes: null,
54
62
  routesDescription: null,
63
+ wsRoutes: null,
64
+ wsRoutesDescription: null,
65
+ wsVerifyClient: null,
55
66
  events: null,
56
67
  relaxChatId: false,
57
68
  bundle: false,
@@ -541,11 +552,13 @@ const ChatExpress = function(options) {
541
552
  var options = chatServer.getOptions();
542
553
  var connector = options.connector;
543
554
  if (connector != null) {
544
- if (options.inboundMessageEvent != null) {
555
+ if (_.isFunction(options.inboundMessageEvent)) {
545
556
  connector.off(options.inboundMessageEvent);
546
557
  }
547
558
  _(events).each(function (callback, eventName) {
548
- connector.off(eventName);
559
+ if (_.isFunction(eventName)) {
560
+ connector.off(eventName);
561
+ }
549
562
  });
550
563
  }
551
564
  }
@@ -562,7 +575,7 @@ const ChatExpress = function(options) {
562
575
 
563
576
  function unmountRoutes(RED, routes, chatServer) {
564
577
  if (routes != null) {
565
- const endpoints = _(routes).keys();
578
+ const endpoints = _.keys(routes);
566
579
  if (!_.isEmpty(endpoints)) {
567
580
  let routesCount = RED.httpNode._router.stack.length;
568
581
  let idx = 0;
@@ -571,7 +584,7 @@ const ChatExpress = function(options) {
571
584
  const route = stack[idx];
572
585
  if (route != null && route.name != null) {
573
586
  const routeName = String(route.name).replace('bound ', '');
574
- if (_.contains(endpoints, routeName)) {
587
+ if (_.includes(endpoints, routeName)) {
575
588
  stack.splice(idx, 1);
576
589
  } else {
577
590
  idx += 1;
@@ -611,7 +624,7 @@ const ChatExpress = function(options) {
611
624
  console.log(lcd.timestamp() + '');
612
625
  // eslint-disable-next-line no-console
613
626
  console.log(lcd.timestamp() + grey('------ WebHooks for ' + options.transport.toUpperCase() + '----------------'));
614
- _(routes).map((middleware, route) => {
627
+ _.each(routes, (middleware, route) => {
615
628
  const host = 'http://localhost' + (uiPort != '80' ? ':' + uiPort : '');
616
629
  const callback = generateCallback(route, chatServer);
617
630
  // make description
@@ -634,6 +647,145 @@ const ChatExpress = function(options) {
634
647
  return when(true);
635
648
  }
636
649
 
650
+ /**
651
+ * @method wsPathname
652
+ * The pathname an upgrade request was sent to, without the query string
653
+ * @param {string} url
654
+ * @return {string}
655
+ */
656
+ function wsPathname(url) {
657
+ const pathname = String(url != null ? url : '').split('?')[0];
658
+ // a trailing slash is the same endpoint: "/redbot/my-bot/ws/" === "/redbot/my-bot/ws"
659
+ return pathname.length > 1 && pathname.slice(-1) === '/' ? pathname.slice(0, -1) : pathname;
660
+ }
661
+
662
+ /**
663
+ * @method wsFullPath
664
+ * Absolute path of a WebSocket endpoint. Like the Express ones, the paths declared by a platform are
665
+ * relative to `httpNodeRoot`, but an upgrade request never enters the Express router so the prefix
666
+ * has to be applied by hand
667
+ * @param {object} RED
668
+ * @param {string} route
669
+ * @return {string}
670
+ */
671
+ function wsFullPath(RED, route) {
672
+ const root = RED.settings != null && !_.isEmpty(RED.settings.httpNodeRoot) ?
673
+ RED.settings.httpNodeRoot : '/';
674
+ const prefix = root.slice(-1) === '/' ? root.slice(0, -1) : root;
675
+ return `${prefix}${route.charAt(0) === '/' ? route : `/${route}`}`;
676
+ }
677
+
678
+ /**
679
+ * @method dispatchUpgrade
680
+ * The one and only "upgrade" listener of the process: hand the request over to the WebSocket endpoint
681
+ * registered on that path, if any.
682
+ * Node-RED attaches its own listeners to the same server (the editor "comms" channel and the core
683
+ * WebSocket nodes) and, exactly like them, a request that matches nothing here is left alone instead
684
+ * of being destroyed, so that the other listeners can still handle it
685
+ */
686
+ function dispatchUpgrade(request, socket, head) {
687
+ const entry = _wsRoutes[wsPathname(request.url)];
688
+ if (entry == null) {
689
+ // not one of ours: don't destroy the socket, another listener may want to handle it
690
+ return;
691
+ }
692
+ entry.wss.handleUpgrade(request, socket, head, function done(connection) {
693
+ entry.wss.emit('connection', connection, request);
694
+ });
695
+ }
696
+
697
+ /**
698
+ * @method mountUpgradeListener
699
+ * Attach the dispatcher to the http server, once per server: a Node-RED deploy re-creates every
700
+ * configuration node, so a listener per chatbot would pile up at every deploy
701
+ * @param {object} server
702
+ */
703
+ function mountUpgradeListener(server) {
704
+ if (_wsServers.has(server)) {
705
+ return;
706
+ }
707
+ server.on('upgrade', dispatchUpgrade);
708
+ _wsServers.add(server);
709
+ }
710
+
711
+ /**
712
+ * @method mountWsRoutes
713
+ * Mount the WebSocket endpoints of a chat server. Unlike the Express routes these are matched exactly,
714
+ * so `wsRoutes` can also be a function (bound to the chat server) returning the routes, for a platform
715
+ * that needs to build the path out of its own configuration (i.e. a bot id in the middle of the path)
716
+ * @param {object} RED
717
+ * @param {object|function} wsRoutes
718
+ * @param {object} wsRoutesDescription
719
+ * @param {object} chatServer
720
+ */
721
+ // eslint-disable-next-line max-params
722
+ function mountWsRoutes(RED, wsRoutes, wsRoutesDescription, chatServer) {
723
+ const routes = _.isFunction(wsRoutes) ? wsRoutes.call(chatServer) : wsRoutes;
724
+ if (routes == null || _.isEmpty(routes)) {
725
+ return when(true);
726
+ }
727
+ // the descriptions are keyed by route: when the routes are generated, so are their descriptions
728
+ const descriptions = _.isFunction(wsRoutesDescription) ?
729
+ wsRoutesDescription.call(chatServer) : wsRoutesDescription;
730
+ if (RED == null || RED.server == null) {
731
+ chatServer.warn('Impossible to mount the WebSocket endpoints: ' +
732
+ (RED == null ? '"RED" param is empty' : '"RED.server" is not available'));
733
+ return when(true);
734
+ }
735
+ const options = chatServer.getOptions();
736
+ const uiPort = RED.settings.get('uiPort');
737
+ mountUpgradeListener(RED.server);
738
+ // eslint-disable-next-line no-console
739
+ console.log(lcd.timestamp() + '');
740
+ // eslint-disable-next-line no-console
741
+ console.log(lcd.timestamp() + grey('------ WebSockets for ' + options.transport.toUpperCase() + '--------------'));
742
+ _.each(routes, (handler, route) => {
743
+ const host = 'ws://localhost' + (uiPort != '80' ? ':' + uiPort : '');
744
+ const path = wsFullPath(RED, generateCallback(route, chatServer));
745
+ if (_wsRoutes[path] != null) {
746
+ chatServer.error(`The WebSocket endpoint ${path} is already mounted by another chatbot, skipped`);
747
+ return null;
748
+ }
749
+ let description = null;
750
+ if (descriptions != null && _.isString(descriptions[route])) {
751
+ description = descriptions[route];
752
+ } else if (descriptions != null && _.isFunction(descriptions[route])) {
753
+ description = descriptions[route].call(chatServer);
754
+ }
755
+ // eslint-disable-next-line no-console
756
+ console.log(lcd.timestamp() + green(host + path) + (description != null ? grey(' - ') + white(description) : ''));
757
+ const wss = new ws.Server(_.isFunction(options.wsVerifyClient) ?
758
+ { noServer: true, verifyClient: options.wsVerifyClient.bind(chatServer) } : { noServer: true });
759
+ wss.setMaxListeners(0);
760
+ wss.on('connection', handler.bind(chatServer));
761
+ wss.on('error', error => chatServer.error(`WebSocket endpoint ${path}: ${error.message}`));
762
+ _wsRoutes[path] = { wss: wss, chatServer: chatServer };
763
+ return null;
764
+ });
765
+ // eslint-disable-next-line no-console
766
+ console.log(lcd.timestamp() + '');
767
+ return when(true);
768
+ }
769
+
770
+ /**
771
+ * @method unmountWsRoutes
772
+ * Remove every WebSocket endpoint of a chat server. The dispatcher attached to the http server stays
773
+ * there (it's shared by the whole process and it does nothing without a matching endpoint)
774
+ * @param {object} chatServer
775
+ */
776
+ function unmountWsRoutes(chatServer) {
777
+ _.keys(_wsRoutes).forEach(path => {
778
+ const entry = _wsRoutes[path];
779
+ if (entry == null || entry.chatServer !== chatServer) {
780
+ return;
781
+ }
782
+ delete _wsRoutes[path];
783
+ // closing the server terminates the connected clients: the widgets will reconnect once the
784
+ // chatbot is up again, a Node-RED deploy re-creates the configuration nodes
785
+ entry.wss.close();
786
+ });
787
+ }
788
+
637
789
  var methods = {
638
790
 
639
791
  'in': function() {
@@ -687,8 +839,8 @@ const ChatExpress = function(options) {
687
839
  if (type == null || typeof type !== 'string') {
688
840
  throw 'Missing type in .registerMessageType()';
689
841
  }
690
- name = name != null ? name : _s.capitalize(type);
691
- let typeDescriptor = _(_messageTypes).findWhere({ type: type });
842
+ name = name != null ? name : _.upperFirst(type);
843
+ let typeDescriptor = _.find(_messageTypes, { type: type });
692
844
  if (typeDescriptor == null) {
693
845
  typeDescriptor = { type: type };
694
846
  _messageTypes.push(typeDescriptor);
@@ -715,7 +867,7 @@ const ChatExpress = function(options) {
715
867
  if (name == null || typeof name !== 'string') {
716
868
  throw 'Missing name in .registerEvent()';
717
869
  }
718
- var eventDescriptor = _(_events).findWhere({ name: name });
870
+ var eventDescriptor = _.find(_events, { name: name });
719
871
  if (eventDescriptor == null) {
720
872
  eventDescriptor = { name: name };
721
873
  _events.push(eventDescriptor);
@@ -787,15 +939,48 @@ const ChatExpress = function(options) {
787
939
  console.log(prettyjson.render(obj));
788
940
  };
789
941
  this.request = function(options = {}) {
790
- return new Promise(function(resolve, reject) {
791
- request(options, function(error, response, body) {
792
- if (error) {
793
- reject(`Error calling URL ${options.url}`);
794
- } else {
795
- resolve(body);
796
- }
942
+ var url = options.url || options.uri;
943
+ // Translate "request"-style options into a "fetch" call
944
+ if (options.qs != null) {
945
+ var qs = new URLSearchParams(options.qs).toString();
946
+ if (qs) {
947
+ url += (url.indexOf('?') === -1 ? '?' : '&') + qs;
948
+ }
949
+ }
950
+ var headers = _.extend({}, options.headers);
951
+ var body;
952
+ if (options.json != null && options.json !== true && options.json !== false) {
953
+ body = JSON.stringify(options.json);
954
+ if (headers['content-type'] == null && headers['Content-Type'] == null) {
955
+ headers['Content-Type'] = 'application/json';
956
+ }
957
+ } else if (options.json === true && options.body != null) {
958
+ body = JSON.stringify(options.body);
959
+ if (headers['content-type'] == null && headers['Content-Type'] == null) {
960
+ headers['Content-Type'] = 'application/json';
961
+ }
962
+ } else if (options.form != null) {
963
+ body = new URLSearchParams(options.form).toString();
964
+ if (headers['content-type'] == null && headers['Content-Type'] == null) {
965
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
966
+ }
967
+ } else if (options.body != null) {
968
+ body = options.body;
969
+ }
970
+ return fetch(url, {
971
+ method: options.method || 'GET',
972
+ headers: headers,
973
+ body: body
974
+ })
975
+ .then(function(response) {
976
+ // Preserve the previous behaviour ({ encoding: null }) of resolving with a Buffer
977
+ return response.arrayBuffer().then(function(buffer) {
978
+ return Buffer.from(buffer);
979
+ });
980
+ })
981
+ .catch(function() {
982
+ return Promise.reject(`Error calling URL ${url}`);
797
983
  });
798
- });
799
984
  };
800
985
  this.getOptions = function() {
801
986
  return this.options;
@@ -815,7 +1000,7 @@ const ChatExpress = function(options) {
815
1000
  return outboundMessage(message, this);
816
1001
  } else {
817
1002
  var task = when(true);
818
- _(message.payload).each(function(payload) {
1003
+ _.each(message.payload, function(payload) {
819
1004
  task = task.then(function() {
820
1005
  return outboundMessage(_.extend({}, message, { payload: payload }), _this);
821
1006
  });
@@ -861,8 +1046,8 @@ const ChatExpress = function(options) {
861
1046
  if (type == null || typeof type !== 'string') {
862
1047
  throw 'Missing type in .registerMessageType()';
863
1048
  }
864
- name = name != null ? name : _s.capitalize(type);
865
- let typeDescriptor = _(_messageTypes).findWhere({ type: type });
1049
+ name = name != null ? name : _.upperFirst(type);
1050
+ let typeDescriptor = _.find(_messageTypes, { type: type });
866
1051
  if (typeDescriptor == null) {
867
1052
  typeDescriptor = { type: type };
868
1053
  _messageTypes.push(typeDescriptor);
@@ -889,7 +1074,7 @@ const ChatExpress = function(options) {
889
1074
  if (name == null || typeof name !== 'string') {
890
1075
  throw 'Missing name in .registerEvent()';
891
1076
  }
892
- var eventDescriptor = _(_events).findWhere({ name: name });
1077
+ var eventDescriptor = _.find(_events, { name: name });
893
1078
  if (eventDescriptor == null) {
894
1079
  eventDescriptor = { name: name };
895
1080
  _events.push(eventDescriptor);
@@ -972,6 +1157,9 @@ const ChatExpress = function(options) {
972
1157
  .then(function() {
973
1158
  return mountRoutes(options.RED, options.routes, options.routesDescription, _this);
974
1159
  })
1160
+ .then(function() {
1161
+ return mountWsRoutes(options.RED, options.wsRoutes, options.wsRoutesDescription, _this);
1162
+ })
975
1163
  .then(function() {
976
1164
  return mountEvents(options.events, _this);
977
1165
  })
@@ -985,7 +1173,7 @@ const ChatExpress = function(options) {
985
1173
  }
986
1174
  // listen to inbound event
987
1175
  var connector = options.connector;
988
- if (connector != null && options.inboundMessageEvent != null) {
1176
+ if (connector != null && options.inboundMessageEvent != null && _.isFunction(connector.on)) {
989
1177
  connector.on(options.inboundMessageEvent, function (message) {
990
1178
  inboundMessage(message, chatServer);
991
1179
  });
@@ -1060,6 +1248,7 @@ const ChatExpress = function(options) {
1060
1248
  this.emit('stop');
1061
1249
  var options = this.getOptions();
1062
1250
  unmountRoutes(options.RED, options.routes, this);
1251
+ unmountWsRoutes(this);
1063
1252
  unmountEvents(options.events, this);
1064
1253
  var stack = when(true);
1065
1254
  if (_.isFunction(options.onStop)) {
@@ -1086,20 +1275,20 @@ const ChatExpress = function(options) {
1086
1275
  Static methods for ChatExpress
1087
1276
  */
1088
1277
  ChatExpress.getMessageTypes = function() {
1089
- return _(_messageTypes).map(function(item) {
1278
+ return _.map(_messageTypes, function(item) {
1090
1279
  return {
1091
1280
  value: item.type,
1092
1281
  label: item.name,
1093
- platforms: _(item.platforms).keys()
1282
+ platforms: _.keys(item.platforms)
1094
1283
  };
1095
1284
  });
1096
1285
  };
1097
1286
  ChatExpress.getEvents = function() {
1098
- return _(_events).map(function(item) {
1287
+ return _.map(_events, function(item) {
1099
1288
  return {
1100
1289
  value: item.name,
1101
1290
  label: item.description,
1102
- platforms: _(item.platforms).keys()
1291
+ platforms: _.keys(item.platforms)
1103
1292
  };
1104
1293
  });
1105
1294
  };
@@ -1110,19 +1299,18 @@ ChatExpress.getParams = function() {
1110
1299
  function compatibilityTable(items, options) {
1111
1300
  options = _.extend({ column: 'Column' }, options);
1112
1301
  // collect platforms except universal
1113
- var platforms = _(ChatExpress.getPlatforms()).chain()
1114
- .map(function(platform) {
1302
+ var platforms = _.reject(
1303
+ _.map(ChatExpress.getPlatforms(), function(platform) {
1115
1304
  return platform.id;
1116
- })
1117
- .reject(function(id) {
1305
+ }),
1306
+ function(id) {
1118
1307
  return id === 'universal';
1119
- })
1120
- .sort()
1121
- .value();
1308
+ }
1309
+ ).sort();
1122
1310
  // build header
1123
1311
  var head = [options.column];
1124
- _(platforms).each(function(name) {
1125
- head.push(_s.capitalize(name));
1312
+ _.each(platforms, function(name) {
1313
+ head.push(_.upperFirst(name));
1126
1314
  });
1127
1315
  var colAligns = ['left'];
1128
1316
  _.times(platforms.length, function() {
@@ -1137,21 +1325,19 @@ function compatibilityTable(items, options) {
1137
1325
  }
1138
1326
  });
1139
1327
  // message types columns
1140
- _(items).chain()
1141
- .sortBy(function(type) {
1142
- return type.name;
1143
- })
1144
- .each(function(type) {
1145
- var row = [type.name];
1146
- _(platforms).each(function(platform) {
1147
- if (type.platforms[platform]) {
1148
- row.push('');
1149
- } else {
1150
- row.push('');
1151
- }
1152
- });
1153
- table.push(row);
1328
+ _.each(_.sortBy(items, function(type) {
1329
+ return type.name;
1330
+ }), function(type) {
1331
+ var row = [type.name];
1332
+ _.each(platforms, function(platform) {
1333
+ if (type.platforms[platform]) {
1334
+ row.push('✔');
1335
+ } else {
1336
+ row.push('');
1337
+ }
1154
1338
  });
1339
+ table.push(row);
1340
+ });
1155
1341
  // eslint-disable-next-line no-console
1156
1342
  console.log(table.toString());
1157
1343
 
@@ -1172,21 +1358,21 @@ ChatExpress.showCompatibilityChart = function() {
1172
1358
  * @return {Array}
1173
1359
  */
1174
1360
  ChatExpress.getPlatforms = function() {
1175
- var platforms = _(_platforms).keys();
1361
+ var platforms = _.keys(_platforms);
1176
1362
 
1177
- return _(platforms).chain()
1178
- .map(function(platform) {
1363
+ return _.sortBy(
1364
+ _.map(platforms, function(platform) {
1179
1365
  return {
1180
1366
  id: _platforms[platform].id,
1181
1367
  name: _platforms[platform].name,
1182
1368
  universal: _platforms[platform].universal,
1183
1369
  color: _platforms[platform].color
1184
1370
  };
1185
- })
1186
- .sortBy(function(platform) {
1371
+ }),
1372
+ function(platform) {
1187
1373
  return platform.name != null ? platform.name : platform.id;
1188
- })
1189
- .value();
1374
+ }
1375
+ );
1190
1376
  };
1191
1377
 
1192
1378
  /**
@@ -1199,9 +1385,9 @@ ChatExpress.getPlatforms = function() {
1199
1385
  ChatExpress.isSupported = function(platform, type) {
1200
1386
  if (type == null) {
1201
1387
  const platforms = ChatExpress.getPlatforms();
1202
- return _(platforms).findWhere({ id: platform }) != null;
1388
+ return _.find(platforms, { id: platform }) != null;
1203
1389
  } else {
1204
- const messageType = _(_messageTypes).findWhere({ type: type });
1390
+ const messageType = _.find(_messageTypes, { type: type });
1205
1391
  return messageType != null && messageType.platforms[platform];
1206
1392
  }
1207
1393
  };
@@ -1219,7 +1405,7 @@ ChatExpress.isSupported = function(platform, type) {
1219
1405
  * @return {String} Null if no errors
1220
1406
  */
1221
1407
  ChatExpress.isValidFile = function(platform, type, file) {
1222
- const messageType = _(_messageTypes).findWhere({ type: type });
1408
+ const messageType = _.find(_messageTypes, { type: type });
1223
1409
  if (messageType != null) {
1224
1410
  const validator = messageType.validators[platform];
1225
1411
  if (validator != null) {
package/helpers/lcd.js CHANGED
@@ -1,7 +1,14 @@
1
1
  var clc = require('cli-color');
2
- var _ = require('underscore');
2
+ var _ = require('lodash');
3
3
  var prettyjson = require('prettyjson');
4
- var moment = require('moment');
4
+
5
+ var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
6
+ // Format a date as 'DD MMM HH:mm:ss' (e.g. '16 Jun 14:30:45')
7
+ function formatTimestamp(date) {
8
+ var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
9
+ return pad(date.getDate()) + ' ' + MONTHS[date.getMonth()] + ' '
10
+ + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
11
+ }
5
12
 
6
13
  var warn = clc.yellow;
7
14
  var grey = clc.blackBright;
@@ -22,7 +29,7 @@ var LCD = {
22
29
  red: red,
23
30
 
24
31
  timestamp() {
25
- return LCD.white(moment().format('DD MMM HH:mm:ss') + ' - [info] ');
32
+ return LCD.white(formatTimestamp(new Date()) + ' - [info] ');
26
33
  },
27
34
 
28
35
  dump: function(e, title) {
@@ -54,7 +61,7 @@ var LCD = {
54
61
  _(payload).each(function(value, key) {
55
62
  if (value instanceof Buffer) {
56
63
  payload[key] = '<Buffer>';
57
- } else if (value instanceof moment) {
64
+ } else if (value instanceof Date) {
58
65
  payload[key] = value.toString();
59
66
  }
60
67
  });
@@ -1,9 +1,9 @@
1
- var _ = require('underscore');
1
+ var _ = require('lodash');
2
2
 
3
3
  var FileQueue = function() {
4
4
  var tasks = [];
5
5
  function removeTask(promise) {
6
- tasks = _(tasks).reject(function(task) {
6
+ tasks = _.reject(tasks, function(task) {
7
7
  return task === promise;
8
8
  });
9
9
  }
package/helpers/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
 
3
3
  module.exports = {
4
4
 
package/lib/lcd.js CHANGED
@@ -1,7 +1,14 @@
1
1
  var clc = require('cli-color');
2
- var _ = require('underscore');
2
+ var _ = require('lodash');
3
3
  var prettyjson = require('prettyjson');
4
- var moment = require('moment');
4
+
5
+ var MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
6
+ // Format a date as 'DD MMM HH:mm:ss' (e.g. '16 Jun 14:30:45')
7
+ function formatTimestamp(date) {
8
+ var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
9
+ return pad(date.getDate()) + ' ' + MONTHS[date.getMonth()] + ' '
10
+ + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
11
+ }
5
12
 
6
13
  var warn = clc.yellow;
7
14
  var grey = clc.blackBright;
@@ -22,7 +29,7 @@ var LCD = {
22
29
  red: red,
23
30
 
24
31
  timestamp() {
25
- return LCD.white(moment().format('DD MMM HH:mm:ss') + ' - [info] ');
32
+ return LCD.white(formatTimestamp(new Date()) + ' - [info] ');
26
33
  },
27
34
 
28
35
  dump: function(e, title) {
@@ -57,7 +64,7 @@ var LCD = {
57
64
  _(payload).each(function(value, key) {
58
65
  if (value instanceof Buffer) {
59
66
  payload[key] = '<Buffer>';
60
- } else if (value instanceof moment) {
67
+ } else if (value instanceof Date) {
61
68
  payload[key] = value.toString();
62
69
  }
63
70
  });
package/lib/red-stub.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable */
2
- var _ = require('underscore');
2
+ var _ = require('lodash');
3
3
  var ChatContextFactory = require('../chat-context-factory')({});
4
4
  var ChatContextProvider = ChatContextFactory.getProvider('memory', {});
5
5
 
@@ -38,7 +38,7 @@ module.exports = function() {
38
38
 
39
39
  environment: {
40
40
  chat: function(chatId, obj) {
41
- _(obj).map(function(value, key) {
41
+ _.each(obj, function(value, key) {
42
42
  _chatContext.set(key, value);
43
43
  });
44
44
  }
package/lib/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
 
3
3
  module.exports = {
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-platform",
3
- "version": "2.1.4",
3
+ "version": "3.1.0",
4
4
  "description": "Universal Chat Platform",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -10,16 +10,17 @@
10
10
  },
11
11
  "author": "",
12
12
  "license": "ISC",
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
13
16
  "dependencies": {
14
17
  "cli-color": "^1.4.0",
15
18
  "cli-table": "^0.3.1",
16
- "moment": "^2.24.0",
19
+ "lodash": "^4.18.1",
17
20
  "prettyjson": "^1.2.1",
18
- "request": "^2.88.0",
19
- "sequelize": "^5.21.6",
20
- "sqlite3": "^5.0.0",
21
- "underscore": "^1.9.1",
22
- "underscore.string": "^3.3.5"
21
+ "sequelize": "^6.37.8",
22
+ "sqlite3": "^5.1.7",
23
+ "ws": "^7.5.7"
23
24
  },
24
25
  "devDependencies": {
25
26
  "chai": "^4.1.1",
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
 
3
3
  const isEmpty = value => value == null || value === '';
4
4
 
@@ -1,6 +1,5 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
  const fs = require('fs');
3
- const moment = require('moment');
4
3
  const crypto = require('crypto');
5
4
 
6
5
  const lcd = require('../helpers/lcd');
@@ -20,7 +19,7 @@ const parse = content => {
20
19
  // go through every key/value to search for a date-like string
21
20
  _(obj).each(function(value, key) {
22
21
  if (_.isString(value) && value.match(date)) {
23
- obj[key] = moment(value);
22
+ obj[key] = new Date(value);
24
23
  }
25
24
  });
26
25
 
@@ -241,7 +240,7 @@ _.extend(FileStore.prototype, {
241
240
  // go through every key/value to search for a date-like string
242
241
  _(obj).each((value, key) => {
243
242
  if (_.isString(value) && value.match(date)) {
244
- obj[key] = moment(value);
243
+ obj[key] = new Date(value);
245
244
  }
246
245
  });
247
246
  } catch(e) {
@@ -1,4 +1,4 @@
1
- const _ = require('underscore');
1
+ const _ = require('lodash');
2
2
 
3
3
  const _storeUserIds = {};
4
4
  const Sequelize = require('sequelize');
package/universal.js CHANGED
@@ -1,8 +1,7 @@
1
- const moment = require('moment');
2
1
  const ChatExpress = require('./chat-platform');
3
2
  const utils = require('./lib/utils');
4
3
  const when = utils.when;
5
- const _ = require('underscore');
4
+ const _ = require('lodash');
6
5
 
7
6
  const Universal = new ChatExpress({
8
7
  transport: 'universal',
@@ -14,7 +13,7 @@ const Universal = new ChatExpress({
14
13
  return payload.userId;
15
14
  },
16
15
  tsKey() {
17
- return moment();
16
+ return new Date();
18
17
  },
19
18
  language() {
20
19
  return null;