wingbot 3.68.17 → 3.69.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.68.17",
3
+ "version": "3.69.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/src/Request.js CHANGED
@@ -1541,7 +1541,8 @@ It looks like the bot isn't connected to class BotApp or the Processor is used w
1541
1541
  campaign,
1542
1542
  timestamp = makeTimestamp(),
1543
1543
  data = null,
1544
- taskId = null
1544
+ taskId = null,
1545
+ setState = null
1545
1546
  ) {
1546
1547
  const postback = Request.postBack(
1547
1548
  senderId,
@@ -1551,6 +1552,11 @@ It looks like the bot isn't connected to class BotApp or the Processor is used w
1551
1552
  {},
1552
1553
  timestamp
1553
1554
  );
1555
+
1556
+ if (setState) {
1557
+ Object.assign(postback.postback.payload, { setState });
1558
+ }
1559
+
1554
1560
  return Object.assign(postback, {
1555
1561
  campaign,
1556
1562
  taskId
@@ -7,6 +7,7 @@ const apiAuthorizer = require('./apiAuthorizer');
7
7
  const { apiTextOutputFilter, mapObject, defaultMapperFactory } = require('../utils/deepMapTools');
8
8
 
9
9
  /** @typedef {import('../utils/deepMapTools').Mapper} Mapper */
10
+ /** @typedef {import('../notifications/Notifications')} Notifications */
10
11
 
11
12
  /**
12
13
  * @typedef {object} ConversationsAPI
@@ -20,11 +21,6 @@ const { apiTextOutputFilter, mapObject, defaultMapperFactory } = require('../uti
20
21
  * @prop {Function} getState
21
22
  */
22
23
 
23
- /**
24
- * @typedef {object} Notifications
25
- * @prop {Function} getSubscribtions
26
- */
27
-
28
24
  /**
29
25
  * @typedef {object} ChatLogStorage
30
26
  * @prop {Function} getInteractions
@@ -92,6 +88,28 @@ function conversationsApi (
92
88
  return data;
93
89
  }
94
90
 
91
+ async function subscriptions () {
92
+ const { pageId, senderId } = this;
93
+
94
+ if (!notifications) {
95
+ return null;
96
+ }
97
+
98
+ if (typeof notifications.getSubscriptions === 'function') {
99
+ return notifications.getSubscriptions(senderId, pageId);
100
+ }
101
+
102
+ if (typeof notifications.getSubscribtions === 'function') {
103
+ const sups = await notifications.getSubscribtions(senderId, pageId);
104
+ return sups.map((s) => ({
105
+ tag: s,
106
+ meta: {}
107
+ }));
108
+ }
109
+
110
+ return null;
111
+ }
112
+
95
113
  async function subscribtions () {
96
114
  if (!notifications || typeof notifications.getSubscribtions !== 'function') {
97
115
  return null;
@@ -107,21 +125,24 @@ function conversationsApi (
107
125
  state: apiTextOutputFilter(d.state, options.stateTextFilter),
108
126
  lastInteraction: d.lastInteraction || (new Date(0)),
109
127
  history,
110
- subscribtions
128
+ subscribtions,
129
+ subscriptions
111
130
  });
112
131
  } else if (options.mapper) {
113
132
  mapState = (d) => ({
114
133
  ...mapObject(d, defaultMapperFactory(options.mapper)),
115
134
  lastInteraction: d.lastInteraction || (new Date(0)),
116
135
  history,
117
- subscribtions
136
+ subscribtions,
137
+ subscriptions
118
138
  });
119
139
  } else {
120
140
  mapState = (d) => ({
121
141
  ...d,
122
142
  lastInteraction: d.lastInteraction || (new Date(0)),
123
143
  history,
124
- subscribtions
144
+ subscribtions,
145
+ subscriptions
125
146
  });
126
147
  }
127
148
 
@@ -121,6 +121,11 @@ type UserInteraction {
121
121
  err: String
122
122
  }
123
123
 
124
+ type Subscription {
125
+ tag: String!
126
+ meta: Any
127
+ }
128
+
124
129
  type Conversation {
125
130
  senderId: String!
126
131
  pageId: String!
@@ -128,6 +133,7 @@ type Conversation {
128
133
  name: String
129
134
  state: Any!
130
135
  subscribtions: [String]
136
+ subscriptions: [Subscription!]
131
137
  history (
132
138
  limit: Int! = 10
133
139
  startTimestamp: Float
@@ -190,6 +196,14 @@ input CreateCampaignInput {
190
196
  condition: String
191
197
  }
192
198
 
199
+ input SubscriptionData {
200
+ pageId: String!
201
+ senderId: String!
202
+ tags: [String!]!
203
+ remove: Boolean = false
204
+ meta: Any
205
+ }
206
+
193
207
  input UpdateCampaignInput {
194
208
  name: String
195
209
  action: String
@@ -303,4 +317,7 @@ type Mutation {
303
317
 
304
318
  "subscribe users"
305
319
  subscribeUsers(pageId:String!, senderIds:[String!]!, tag: String): Boolean
320
+
321
+ "subscribe with metadata"
322
+ subscribeWithData (subscriptions: [SubscriptionData!]!): Boolean
306
323
  }
@@ -104,25 +104,26 @@ const DEFAULT_CAMPAIGN_DATA = {
104
104
  */
105
105
 
106
106
  /**
107
- * @typedef {object} Subscription
108
- * @prop {string} senderId
109
- * @prop {string} pageId
110
- * @prop {string} [meta]
107
+ * @typedef {object} SenderSubscription
108
+ * @prop {string} tag
109
+ * @prop {object} meta
111
110
  */
112
111
 
113
112
  /**
114
- * @callback PreprocessSubscriptions
115
- * @param {Subscription[]} subscriptions
116
- * @param {string} pageId
117
- * @returns {Promise<Subscription[]>|Subscription[]}
113
+ * @typedef {object} Logger
114
+ * @prop {Function} log
115
+ * @prop {Function} error
118
116
  */
119
117
 
118
+ /** @typedef {import('./api/notificationsApiFactory').NotificationsApiOptions} ApiOptions */
119
+
120
120
  /**
121
- * @callback PreprocessSubscribers
122
- * @param {string[]} senderIds
123
- * @param {string} pageId
124
- * @param {string} tag
125
- * @returns {Promise<string[]>|string[]} list of senderIds
121
+ * @typedef {object} NotificationsServiceOptions
122
+ * @prop {Logger} [log]
123
+ * @prop {number} [options.default24Clearance] - use this clearance to ensure delivery in 24h
124
+ * @prop {string} [options.allAudienceTag] - tag to mark all users
125
+ *
126
+ * @typedef {ApiOptions & NotificationsServiceOptions} NotificationsOptions
126
127
  */
127
128
 
128
129
  /**
@@ -139,12 +140,7 @@ class Notifications extends EventEmitter {
139
140
  * @memberof Notifications
140
141
  *
141
142
  * @param {NotificationsStorage} notificationStorage
142
- * @param {object} options
143
- * @param {console} [options.log] - logger
144
- * @param {number} [options.default24Clearance] - use this clearance to ensure delivery in 24h
145
- * @param {string} [options.allAudienceTag] - tag to mark all users
146
- * @param {PreprocessSubscribers} [options.preprocessSubscribers] - preprocess senderIds import
147
- * @param {PreprocessSubscriptions} [options.preprocessSubscriptions]
143
+ * @param {NotificationsOptions} options
148
144
  */
149
145
  constructor (notificationStorage = new NotificationsStorage(), options = {}) {
150
146
  super();
@@ -407,6 +403,18 @@ class Notifications extends EventEmitter {
407
403
  return this._storage.getSenderSubscribtions(senderId, pageId);
408
404
  }
409
405
 
406
+ /**
407
+ *
408
+ * Get user subscribtions
409
+ *
410
+ * @param {string} senderId
411
+ * @param {string} pageId
412
+ * @returns {Promise<SenderSubscription[]>}
413
+ */
414
+ async getSubscriptions (senderId, pageId) {
415
+ return this._storage.getSenderSubscriptions(senderId, pageId);
416
+ }
417
+
410
418
  async _preloadSubscribtions (req, res) {
411
419
  if (res.data._requestSubscribtions) {
412
420
  req.subscribtions = res.data._requestSubscribtions;
@@ -788,24 +796,38 @@ class Notifications extends EventEmitter {
788
796
 
789
797
  let queued = 0;
790
798
 
799
+ const campaignData = campaign.data || {};
800
+
791
801
  while (hasUsers) {
792
802
  const { data: targets, lastKey: key } = await this._storage
793
803
  .getSubscribtions(include, exclude, this.limit, campaign.pageId, lastKey);
794
804
 
795
805
  lastKey = key;
796
806
 
797
- const campaignTargets = targets.map((target) => ({
798
- senderId: target.senderId,
799
- pageId: target.pageId,
800
- campaignId: campaign.id,
801
- enqueue
802
- }));
807
+ const campaignTargets = targets.map((target) => {
808
+ const data = include.length === 0
809
+ ? campaignData
810
+ : Object.assign(
811
+ {},
812
+ campaignData,
813
+ // @ts-ignore
814
+ ...include.map((t) => (target.meta && target.meta[t]) || {})
815
+ );
816
+
817
+ return {
818
+ senderId: target.senderId,
819
+ pageId: target.pageId,
820
+ campaignId: campaign.id,
821
+ enqueue,
822
+ data
823
+ };
824
+ });
803
825
 
804
826
  const actions = await this.pushTasksToQueue(campaignTargets);
805
827
 
806
828
  queued += actions.length;
807
829
 
808
- hasUsers = targets.length > 0 && lastKey;
830
+ hasUsers = targets.length > 0 && !!lastKey;
809
831
  }
810
832
 
811
833
  return { queued };
@@ -910,7 +932,15 @@ class Notifications extends EventEmitter {
910
932
  return { status: 204 };
911
933
  }
912
934
 
913
- const message = Request.campaignPostBack(task.senderId, campaign, ts, task.data, task.id);
935
+ const message = Request.campaignPostBack(
936
+ task.senderId,
937
+ campaign,
938
+ ts,
939
+ task.data,
940
+ task.id,
941
+ task.data
942
+ );
943
+
914
944
  let status;
915
945
  let mid;
916
946
 
@@ -6,26 +6,28 @@
6
6
  const uuid = require('uuid');
7
7
 
8
8
  /**
9
- * @typedef Tag {Object}
9
+ * @typedef {object} Tag
10
10
  * @prop {string} tag
11
11
  * @prop {number} subscribtions
12
12
  */
13
13
 
14
14
  /**
15
- * @typedef Target {Object}
15
+ * @typedef {object} Target
16
16
  * @prop {string} senderId
17
17
  * @prop {string} pageId
18
+ * @prop {{ [key: string]: object }} [meta]
18
19
  */
19
20
 
20
21
  /**
21
- * @typedef Subscribtion {Object}
22
+ * @typedef {object} Subscribtion
22
23
  * @prop {string} senderId
23
24
  * @prop {string} pageId
24
25
  * @prop {string[]} subs
26
+ * @prop {{ [key: string]: object }} [meta]
25
27
  */
26
28
 
27
29
  /**
28
- * @typedef Campaign {object}
30
+ * @typedef {object} Campaign
29
31
  * @prop {string} id
30
32
  * @prop {string} name
31
33
  *
@@ -63,7 +65,7 @@ const uuid = require('uuid');
63
65
  */
64
66
 
65
67
  /**
66
- * @typedef Task {Object}
68
+ * @typedef {object} Task
67
69
  * @prop {string} id
68
70
  * @prop {string} pageId
69
71
  * @prop {string} senderId
@@ -77,6 +79,21 @@ const uuid = require('uuid');
77
79
  * @prop {number} [leaved] - time the event was not sent because user left
78
80
  */
79
81
 
82
+ /**
83
+ * @typedef {object} Subscription
84
+ * @prop {string} tag
85
+ * @prop {object} meta
86
+ */
87
+
88
+ /**
89
+ * @typedef {object} SubscriptionData
90
+ * @prop {string} pageId
91
+ * @prop {string} senderId
92
+ * @prop {string[]} tags
93
+ * @prop {boolean} [remove]
94
+ * @prop {{ [key: string]: object }} [meta]
95
+ */
96
+
80
97
  const MAX_TS = 9999999999999;
81
98
 
82
99
  class NotificationsStorage {
@@ -450,9 +467,9 @@ class NotificationsStorage {
450
467
  * @param {string} senderId
451
468
  * @param {string} pageId
452
469
  * @param {string} tag
453
- * @returns {Promise}
470
+ * @param {{}} [meta={}]
454
471
  */
455
- _subscribe (senderId, pageId, tag) {
472
+ _subscribe (senderId, pageId, tag, meta = {}) {
456
473
  const key = `${senderId}|${pageId}`;
457
474
  let subscribtion = this._subscribtions.get(key);
458
475
  if (!subscribtion) {
@@ -463,20 +480,60 @@ class NotificationsStorage {
463
480
  };
464
481
  }
465
482
  if (!subscribtion.subs.includes(tag)) {
466
- subscribtion = { ...subscribtion, subs: [...subscribtion.subs, tag] };
483
+ subscribtion = {
484
+ ...subscribtion,
485
+ subs: [...subscribtion.subs, tag]
486
+ };
487
+ if (meta && Object.keys(meta).length) {
488
+ Object.assign(subscribtion, {
489
+ meta: {
490
+ ...subscribtion.meta,
491
+ [tag]: meta
492
+ }
493
+ });
494
+ } else if (subscribtion.meta && subscribtion.meta[tag]) {
495
+ delete subscribtion.meta;
496
+ }
467
497
  }
468
498
 
469
499
  this._subscribtions.set(key, subscribtion);
470
500
  }
471
501
 
502
+ /**
503
+ *
504
+ * @param {SubscriptionData[]} subscriptionData
505
+ * @param {boolean} [onlyToKnown]
506
+ * @returns {Promise}
507
+ */
508
+ // eslint-disable-next-line no-unused-vars
509
+ async batchSubscribe (subscriptionData, onlyToKnown = null) {
510
+ subscriptionData.forEach(({
511
+ senderId, pageId, tags, remove = false, meta = {}
512
+ }) => {
513
+ if (remove) {
514
+ tags.forEach((tag) => {
515
+ this._unsubscribe(senderId, pageId, tag);
516
+ });
517
+ } else {
518
+ tags.forEach((tag) => {
519
+ this._subscribe(senderId, pageId, tag, meta[tag]);
520
+ });
521
+ }
522
+ });
523
+
524
+ return Promise.resolve();
525
+ }
526
+
472
527
  /**
473
528
  *
474
529
  * @param {string|string[]} senderId
475
530
  * @param {string} pageId
476
531
  * @param {string} tag
532
+ * @param {boolean} [onlyToKnown]
477
533
  * @returns {Promise}
478
534
  */
479
- subscribe (senderId, pageId, tag) {
535
+ // eslint-disable-next-line no-unused-vars
536
+ subscribe (senderId, pageId, tag, onlyToKnown = null) {
480
537
  const insert = Array.isArray(senderId) ? senderId : [senderId];
481
538
  insert.forEach((sender) => this._subscribe(sender, pageId, tag));
482
539
  return Promise.resolve();
@@ -490,9 +547,14 @@ class NotificationsStorage {
490
547
  * @returns {Promise<string[]>}
491
548
  */
492
549
  unsubscribe (senderId, pageId, tag = null) {
550
+ const unsubscribtions = this._unsubscribe(senderId, pageId, tag);
551
+ return Promise.resolve(unsubscribtions);
552
+ }
553
+
554
+ _unsubscribe (senderId, pageId, tag = null) {
493
555
  const key = `${senderId}|${pageId}`;
494
556
  if (!this._subscribtions.has(key)) {
495
- return Promise.resolve([]);
557
+ return [];
496
558
  }
497
559
  const unsubscribtions = [];
498
560
  let subscribtion = this._subscribtions.get(key);
@@ -507,12 +569,15 @@ class NotificationsStorage {
507
569
  return !out;
508
570
  })
509
571
  };
572
+ if ('meta' in subscribtion && subscribtion.meta[tag]) {
573
+ delete subscribtion.meta[tag];
574
+ }
510
575
  if (subscribtion.subs.length === 0) {
511
576
  this._subscribtions.delete(key);
512
577
  } else {
513
578
  this._subscribtions.set(key, subscribtion);
514
579
  }
515
- return Promise.resolve(unsubscribtions);
580
+ return unsubscribtions;
516
581
  }
517
582
 
518
583
  /**
@@ -581,7 +646,8 @@ class NotificationsStorage {
581
646
 
582
647
  const data = ret.map((sub) => ({
583
648
  senderId: sub.senderId,
584
- pageId: sub.pageId
649
+ pageId: sub.pageId,
650
+ ...(sub.meta ? { meta: sub.meta } : {})
585
651
  }));
586
652
 
587
653
  let nextLastKey = null;
@@ -598,12 +664,11 @@ class NotificationsStorage {
598
664
  }
599
665
 
600
666
  /**
601
- *
602
667
  * @param {string} senderId
603
668
  * @param {string} pageId
604
- * @returns {Promise<string[]>}
669
+ * @returns {Promise<Subscription[]>}
605
670
  */
606
- getSenderSubscribtions (senderId, pageId) {
671
+ async getSenderSubscriptions (senderId, pageId) {
607
672
  const key = `${senderId}|${pageId}`;
608
673
 
609
674
  if (!this._subscribtions.has(key)) {
@@ -612,7 +677,21 @@ class NotificationsStorage {
612
677
 
613
678
  const sub = this._subscribtions.get(key);
614
679
 
615
- return Promise.resolve(sub.subs);
680
+ return sub.subs.map((tag) => ({
681
+ tag,
682
+ meta: (sub.meta && sub.meta[tag]) || {}
683
+ }));
684
+ }
685
+
686
+ /**
687
+ *
688
+ * @param {string} senderId
689
+ * @param {string} pageId
690
+ * @returns {Promise<string[]>}
691
+ */
692
+ async getSenderSubscribtions (senderId, pageId) {
693
+ const subs = await this.getSenderSubscriptions(senderId, pageId);
694
+ return subs.map((s) => s.tag);
616
695
  }
617
696
 
618
697
  /**
@@ -5,6 +5,9 @@
5
5
 
6
6
  const apiAuthorizer = require('../../graphApi/apiAuthorizer');
7
7
 
8
+ /** @typedef {import('../NotificationsStorage')} NotificationsStorage */
9
+ /** @typedef {import('../Notifications')} Notifications */
10
+
8
11
  /**
9
12
  *
10
13
  * @param {*} info
@@ -38,6 +41,59 @@ function getFields (info) {
38
41
  }, {});
39
42
  }
40
43
 
44
+ /**
45
+ * @typedef {object} Target
46
+ * @prop {string} senderId
47
+ * @prop {string} pageId
48
+ */
49
+
50
+ /**
51
+ * @typedef {object} SubscriptionData
52
+ * @prop {string} pageId
53
+ * @prop {string} senderId
54
+ * @prop {string[]} tags
55
+ * @prop {boolean} [remove]
56
+ * @prop {{ [key: string]: object }} [meta]
57
+ */
58
+
59
+ /**
60
+ *
61
+ * @callback PreprocessSubscribe
62
+ * @param {SubscriptionData[]} subscriptions
63
+ * @returns {Promise<SubscriptionData[]>}
64
+ */
65
+
66
+ /**
67
+ *
68
+ * @callback PreprocessSubscriptions
69
+ * @param {Target[]} subscriptions
70
+ * @param {string} [pageId]
71
+ * @returns {Promise<Target[]>|Target[]}
72
+ */
73
+
74
+ /**
75
+ *
76
+ * @callback PreprocessSubscribers
77
+ * @param {string[]} senderIds
78
+ * @param {string} pageId
79
+ * @param {string} tag
80
+ * @returns {Promise<string[]>|string[]}
81
+ */
82
+
83
+ /**
84
+ * @typedef {object} NotificationsApiOptions
85
+ * @prop {PreprocessSubscribe} [preprocessSubscribe]
86
+ * @prop {PreprocessSubscriptions} [preprocessSubscriptions]
87
+ * @prop {PreprocessSubscribers} [preprocessSubscribers]
88
+ */
89
+
90
+ /**
91
+ *
92
+ * @param {NotificationsStorage} storage
93
+ * @param {Notifications} notifications
94
+ * @param {*} acl
95
+ * @param {NotificationsApiOptions} options
96
+ */
41
97
  function notificationsApiFactory (storage, notifications, acl, options = {}) {
42
98
  return {
43
99
  async campaigns (args, ctx) {
@@ -213,6 +269,32 @@ function notificationsApiFactory (storage, notifications, acl, options = {}) {
213
269
  return true;
214
270
  },
215
271
 
272
+ /**
273
+ * @typedef {object} SubscriptionData
274
+ * @prop {string} pageId
275
+ * @prop {string} senderId
276
+ * @prop {string[]} tags
277
+ * @prop {boolean} [remove]
278
+ * @prop {{ [key: string]: object }} [meta]
279
+ */
280
+
281
+ /**
282
+ *
283
+ * @param {{ subscriptions: SubscriptionData[] }} args
284
+ * @param {*} ctx
285
+ * @returns {Promise<boolean>}
286
+ */
287
+ async subscribeWithData (args, ctx) {
288
+ if (!apiAuthorizer(args, ctx, acl)) {
289
+ return null;
290
+ }
291
+
292
+ const { subscriptions } = args;
293
+
294
+ await storage.batchSubscribe(subscriptions, true);
295
+ return true;
296
+ },
297
+
216
298
  async tags (args, ctx) {
217
299
  if (!apiAuthorizer(args, ctx, acl)) {
218
300
  return null;
@@ -304,13 +304,14 @@ class CustomEntityDetectionModel {
304
304
  return zLen - aLen;
305
305
  });
306
306
 
307
+ if (this._options.verbose) this._log.log('#NLP [nonOverlapping]', { entities, expectedEntities, justDuplicates });
308
+
307
309
  let res = [];
308
310
 
309
311
  for (let i = 0; i < entities.length; i++) {
310
312
  const entity = entities[i];
311
313
 
312
314
  const isExpected = expectedEntities.includes(entity.entity);
313
-
314
315
  const duplicate = res
315
316
  .find((e) => e.start === entity.start && e.end === entity.end);
316
317
 
@@ -353,6 +354,10 @@ class CustomEntityDetectionModel {
353
354
  const othersConflict = res.some((e) => putback === e
354
355
  || (e.start < putback.end && e.end > putback.start));
355
356
 
357
+ this._log.log(`#NLP (${i}|${k} [putBack: ${entity.entity}|${putback.entity}] (${i}|${k})`, {
358
+ putback, entity, currentConflict, othersConflict
359
+ });
360
+
356
361
  if (!currentConflict && !othersConflict) {
357
362
  res.push(putback);
358
363
  }
@@ -360,6 +365,12 @@ class CustomEntityDetectionModel {
360
365
  }
361
366
  }
362
367
 
368
+ if (this._options.verbose) {
369
+ this._log.log(`#NLP (${i}) [nonOverlapping| ${entity.entity}:${entity.value}]`, {
370
+ willRemoveEntity: overlapping, overlapping, duplicate, isExpected, entity
371
+ });
372
+ }
373
+
363
374
  if (!overlapping) {
364
375
  res.push(entity);
365
376
  }
@@ -102,7 +102,7 @@ class WingbotModel extends CachedModel {
102
102
  `matches=${encodeURIComponent(this._matches)}`
103
103
  ];
104
104
 
105
- if (this._options.verbose) this._log.log(`#NLP: "${text}" [query]`, { matches: this._matches, localEntities: entities });
105
+ if (this._options.verbose) this._log.log(`#NLP: ${text} [query]`, { matches: this._matches, localEntities: entities });
106
106
 
107
107
  if (entities) {
108
108
  const buf = await this._brotli(Buffer.from(JSON.stringify({ entities })));
@@ -123,7 +123,7 @@ class WingbotModel extends CachedModel {
123
123
 
124
124
  const response = await res.json();
125
125
 
126
- if (this._options.verbose) this._log.log(`#NLP: ${text} [response]`, { response });
126
+ if (this._options.verbose) this._log.log(`#NLP: ${text} [response]`, response);
127
127
 
128
128
  if (res.status >= 300) {
129
129
  this._log.warn(`AI query failed: ${response.message || res.statusText}`);