wingbot 3.59.2 → 3.61.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.59.2",
3
+ "version": "3.61.0",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@
9
9
  "doc": "npm run doc:gql && node ./bin/makeApiDoc.js && cpy ./CHANGELOG.md ./doc && gitbook install ./doc && gitbook build ./doc && rimraf -rf ./docs && rimraf --rf ./doc/CHANGELOG.md && move-cli ./doc/_book ./docs",
10
10
  "test": "npm run test:lint && npm run test:coverage && npm run test:coverage:threshold",
11
11
  "test:coverage": "nyc --reporter=html mocha ./test && nyc report",
12
- "test:coverage:threshold": "nyc check-coverage --lines 90 --functions 89 --branches 80",
12
+ "test:coverage:threshold": "nyc check-coverage --lines 89 --functions 89 --branches 80",
13
13
  "test:backend": "mocha ./test",
14
14
  "test:lint": "eslint --ext .js src test *.js plugins"
15
15
  },
@@ -1,10 +1,62 @@
1
+ const { default: fetch } = require('node-fetch');
1
2
  const { compileWithState } = require('../../src/utils');
2
3
 
4
+ function transformEvent (c, a, l, v, req, res) {
5
+ switch (c) {
6
+ case 'generate_lead':
7
+ return {
8
+ name: 'generate_lead',
9
+ params: {
10
+ currency: l || 'USD',
11
+ value: v
12
+ }
13
+ };
14
+ case 'view_item':
15
+ return {
16
+ name: 'view_item',
17
+ params: {
18
+ currency: l || 'USD',
19
+ value: v,
20
+ items: [
21
+ {
22
+ item_name: res.currentAction(),
23
+ item_category: a || ''
24
+ }
25
+ ]
26
+ }
27
+ };
28
+ case 'purchase':
29
+ return {
30
+ name: 'purchase',
31
+ params: {
32
+ currency: l || 'USD',
33
+ transaction_id: `${req.pageId}.${req.senderId}.${req.timestamp}`,
34
+ value: v,
35
+ items: [
36
+ {
37
+ item_name: res.currentAction(),
38
+ item_category: a || ''
39
+ }
40
+ ]
41
+ }
42
+ };
43
+ case 'tutorial_begin':
44
+ case 'tutorial_complete':
45
+ case 'sign_up':
46
+ case 'share':
47
+ return {
48
+ name: c
49
+ };
50
+ default:
51
+ return null;
52
+ }
53
+ }
54
+
3
55
  /**
4
56
  * @param {import('../../src/Request')} req
5
57
  * @param {import('../../src/Responder')} res
6
58
  */
7
- function trackingEvent (req, res) {
59
+ async function trackingEvent (req, res) {
8
60
  const {
9
61
  category = '',
10
62
  action = '',
@@ -13,12 +65,53 @@ function trackingEvent (req, res) {
13
65
  type
14
66
  } = req.params;
15
67
 
16
- const c = compileWithState(req, res, category);
17
- const a = compileWithState(req, res, action);
18
- const l = compileWithState(req, res, label);
68
+ const c = compileWithState(req, res, category).trim();
69
+ const a = compileWithState(req, res, action).trim();
70
+ const l = compileWithState(req, res, label).trim();
19
71
  const v = parseFloat(compileWithState(req, res, value)
20
72
  .replace(/[^0-9.]+/, '')) || 0;
21
73
 
74
+ const {
75
+ '§gi': clientId,
76
+ '§gc': gclid
77
+ } = req.state;
78
+
79
+ const {
80
+ gaMeasurementId,
81
+ gaApiSecret
82
+ } = req.configuration;
83
+
84
+ if (clientId && gaMeasurementId && gaApiSecret) {
85
+
86
+ const ev = transformEvent(c, a, l, v, req, res);
87
+
88
+ const body = {
89
+ client_id: clientId,
90
+ timestamp_micros: req.timestamp * 1000,
91
+ non_personalized_ads: false,
92
+ ...(a ? {
93
+ user_properties: {
94
+ [a]: {
95
+ value: v || 1
96
+ }
97
+ }
98
+ } : {}),
99
+ events: ev ? [ev] : []
100
+ };
101
+
102
+ const params = {
103
+ method: 'POST',
104
+ body: JSON.stringify(body)
105
+ };
106
+
107
+ try {
108
+ await fetch(`https://www.google-analytics.com/mp/collect?api_secret=${encodeURIComponent(gaApiSecret)}&measurement_id=${encodeURIComponent(gaMeasurementId)}`, params);
109
+ } catch (e) {
110
+ // eslint-disable-next-line no-console
111
+ console.error('GA FAILED', e, body);
112
+ }
113
+ }
114
+
22
115
  res.trackEvent(type || 'report', c, a, l, v);
23
116
  }
24
117
 
@@ -0,0 +1,54 @@
1
+ const Router = require('../../src/Router');
2
+
3
+ /**
4
+ *
5
+ * @param {object} params
6
+ * @param {'any'|'image'|'audio'|'video'|'file'} params.type
7
+ * @param {string} params.variable
8
+ * @param {'array'|'string'} params.datatype
9
+ * @returns {import('../../src/Router').Middleware}
10
+ */
11
+ module.exports = (params) => {
12
+
13
+ /**
14
+ * @param {import('../../src/Request')} req
15
+ * @param {import('../../src/Responder')} res
16
+ */
17
+ const uploadPlugin = async (req, res) => {
18
+ if (req.isAttachment()) {
19
+
20
+ const typeOk = (!params.type || params.type === 'any')
21
+ || req.attachments.every((a) => a.type === params.type);
22
+
23
+ if (!typeOk) {
24
+ await res.run('badType');
25
+ return Router.NEXT;
26
+ }
27
+
28
+ const varName = `${params.variable || 'uploadUrl'}`.trim();
29
+
30
+ if (params.datatype === 'array') {
31
+ const curr = req.state[varName] || [];
32
+ const urls = req.attachments.map((a) => a.payload.url)
33
+ .filter((u) => !!u);
34
+
35
+ res.setState({
36
+ [varName]: [...curr, ...urls]
37
+ });
38
+ } else {
39
+ const [first] = req.attachments;
40
+
41
+ res.setState({
42
+ [varName]: first.payload.url || null
43
+ });
44
+ }
45
+ await res.run('success');
46
+
47
+ } else {
48
+ await res.run('noAttachment');
49
+ }
50
+ return Router.NEXT;
51
+ };
52
+
53
+ return uploadPlugin;
54
+ };
@@ -162,17 +162,17 @@
162
162
  {
163
163
  "name": "category",
164
164
  "type": "text",
165
- "label": "Event category"
165
+ "label": "Event category (generate_lead,view_item,purchase,tutorial_begin,tutorial_complete,sign_up,share)"
166
166
  },
167
167
  {
168
168
  "name": "action",
169
169
  "type": "text",
170
- "label": "Event action"
170
+ "label": "Event action (item category)"
171
171
  },
172
172
  {
173
173
  "name": "label",
174
174
  "type": "text",
175
- "label": "Event label"
175
+ "label": "Event label (currency)"
176
176
  },
177
177
  {
178
178
  "name": "value",
@@ -609,6 +609,57 @@
609
609
  ],
610
610
  "items": [
611
611
  ]
612
+ },
613
+ {
614
+ "id": "ai.wingbot.upload",
615
+ "name": "Process upload",
616
+ "description": "Plugin is intended to be used in an interaction plugin",
617
+ "availableSince": 3.60,
618
+ "editable": false,
619
+ "isFactory": true,
620
+ "category": "detectors",
621
+ "inputs": [
622
+ {
623
+ "name": "type",
624
+ "label": "Allowed attachment type",
625
+ "type": "select",
626
+ "options": [
627
+ { "value": "any", "label": "any attachment type" },
628
+ { "value": "image", "label": "image" },
629
+ { "value": "audio", "label": "audio" },
630
+ { "value": "video", "label": "video" },
631
+ { "value": "file", "label": "file" }
632
+ ]
633
+ },
634
+ {
635
+ "name": "variable",
636
+ "label": "Variable name to set attachment URL(s)",
637
+ "type": "text"
638
+ },
639
+ {
640
+ "name": "datatype",
641
+ "label": "Allowed attachment type",
642
+ "type": "select",
643
+ "options": [
644
+ { "value": "string", "label": "string" },
645
+ { "value": "array", "label": "array of strings" }
646
+ ]
647
+ }
648
+ ],
649
+ "items": [
650
+ {
651
+ "id": "noAttachment",
652
+ "description": "No attachment uploaded"
653
+ },
654
+ {
655
+ "id": "badType",
656
+ "description": "Disallowed attachment type"
657
+ },
658
+ {
659
+ "id": "success",
660
+ "description": "Successfully uploaded"
661
+ }
662
+ ]
612
663
  }
613
664
  ],
614
665
  "categories": [
package/src/Request.js CHANGED
@@ -109,6 +109,13 @@ function makeTimestamp () {
109
109
  * @prop {number} score
110
110
  */
111
111
 
112
+ /**
113
+ * @typedef {object} Attachment
114
+ * @prop {'file'|'audio'|'video'|'image'} type
115
+ * @prop {object} payload
116
+ * @prop {string} payload.url
117
+ */
118
+
112
119
  /**
113
120
  * @typedef {number} AiSetStateOption
114
121
  */
@@ -175,6 +182,7 @@ class Request {
175
182
 
176
183
  this._optin = event.optin || null;
177
184
 
185
+ /** @type {Attachment[]} */
178
186
  this.attachments = (event.message
179
187
  && (event.message.attachment
180
188
  ? [event.message.attachment]
package/src/Tester.js CHANGED
@@ -143,6 +143,11 @@ class Tester {
143
143
  * @prop {string[]}
144
144
  */
145
145
  this.features = null;
146
+
147
+ /**
148
+ * @prop {string}
149
+ */
150
+ this.ATTACHMENT_MOCK_URL = 'http://mock.url/file.txt';
146
151
  }
147
152
 
148
153
  _actionHasGlobalIntent (action) {
@@ -415,6 +420,18 @@ class Tester {
415
420
  return this.processMessage(Request.text(this.senderId, text));
416
421
  }
417
422
 
423
+ /**
424
+ * Sends attachment
425
+ *
426
+ * @param {'image'|'audio'|'video'|'file'} type
427
+ * @param {string} [url]
428
+ * @returns {Promise}
429
+ * @memberOf Tester
430
+ */
431
+ attachment (type = 'file', url = this.ATTACHMENT_MOCK_URL) {
432
+ return this.processMessage(Request.fileAttachment(this.senderId, url, type));
433
+ }
434
+
418
435
  /**
419
436
  * Makes recognised AI intent request
420
437
  *