@meltwater/conversations-api-services 1.0.4 → 1.0.5
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
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import superagent from 'superagent';
|
|
2
|
+
import { removePrefix } from '../../lib/externalId.helpers.js';
|
|
3
|
+
import configuration from '../../lib/configuration.js';
|
|
4
|
+
|
|
5
|
+
export class TikTokApiClient {
|
|
6
|
+
constructor({ services }) {
|
|
7
|
+
this.tiktokURL = configuration.get('TIKTOK_API_URL');
|
|
8
|
+
this.tokenService = services.token;
|
|
9
|
+
this.logger = services.logger;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async getAuthorization(document) {
|
|
13
|
+
const {
|
|
14
|
+
documentId,
|
|
15
|
+
systemData: { connectionsCredential: credentialId } = {},
|
|
16
|
+
} = document;
|
|
17
|
+
try {
|
|
18
|
+
const token = await this.tokenService.getByCredentialId(
|
|
19
|
+
credentialId
|
|
20
|
+
);
|
|
21
|
+
return `${token.token}`;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
this.logger.error(
|
|
24
|
+
`${documentId} - error getting tiktok token - ${error.code}`,
|
|
25
|
+
error
|
|
26
|
+
);
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async sendPost({
|
|
32
|
+
paramString = '',
|
|
33
|
+
headers = {},
|
|
34
|
+
document,
|
|
35
|
+
postData = undefined,
|
|
36
|
+
}) {
|
|
37
|
+
let response = {};
|
|
38
|
+
try {
|
|
39
|
+
response = await superagent
|
|
40
|
+
.post(this.tiktokURL + paramString)
|
|
41
|
+
.set('Accept', 'application/json')
|
|
42
|
+
.set('Content-Type', 'application/json')
|
|
43
|
+
.set('Access-Token', await this.getAuthorization(document))
|
|
44
|
+
.send(postData);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (
|
|
47
|
+
err &&
|
|
48
|
+
err.response &&
|
|
49
|
+
err.response.body &&
|
|
50
|
+
err.response.body.error
|
|
51
|
+
) {
|
|
52
|
+
this.logger.error(
|
|
53
|
+
`Failed to call tiktok api for paramString ${paramString}: ${err.response.body.error.message}`
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
this.logger.error(
|
|
57
|
+
`Failed to call tiktok api for paramString ${paramString}`,
|
|
58
|
+
err
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return response;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// assumes batch calls are using the same credential
|
|
69
|
+
async sendRequest({ paramString = '', headers = {}, document }) {
|
|
70
|
+
let response = {};
|
|
71
|
+
try {
|
|
72
|
+
response = await superagent
|
|
73
|
+
.get(this.tiktokURL + paramString)
|
|
74
|
+
.set('Accept', 'application/json')
|
|
75
|
+
.set('Content-Type', 'application/json')
|
|
76
|
+
.set('Access-Token', await this.getAuthorization(document))
|
|
77
|
+
.send();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (
|
|
80
|
+
err &&
|
|
81
|
+
err.response &&
|
|
82
|
+
err.response.body &&
|
|
83
|
+
err.response.body.error
|
|
84
|
+
) {
|
|
85
|
+
this.logger.error(
|
|
86
|
+
`Failed to call tiktok api for paramString ${paramString}: ${err.response.body.error.message}`
|
|
87
|
+
);
|
|
88
|
+
} else {
|
|
89
|
+
this.logger.error(
|
|
90
|
+
`Failed to call tiktok api for paramString ${paramString}`,
|
|
91
|
+
err
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (response.status !== 200) {
|
|
99
|
+
this.logger.error(
|
|
100
|
+
`Failed to call tiktok api for documentId ${documentId}`,
|
|
101
|
+
response.body
|
|
102
|
+
);
|
|
103
|
+
let error = new Error(
|
|
104
|
+
`Failed to call tiktok api for documentId ${documentId}`
|
|
105
|
+
);
|
|
106
|
+
error.code = response.status;
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return response.body;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async getChannelInfoForDocuments(documents) {
|
|
114
|
+
const channelInfo = await this.getStatsForDocuments(
|
|
115
|
+
documents,
|
|
116
|
+
'metaData.authors.0.authorInfo.externalId',
|
|
117
|
+
{
|
|
118
|
+
og: ['video/list/'],
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
return channelInfo;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async getStatsForDocuments(
|
|
125
|
+
documents,
|
|
126
|
+
externalIdFrom = 'metaData.externalId',
|
|
127
|
+
paramsByType = {
|
|
128
|
+
og: ['video/list/'],
|
|
129
|
+
}
|
|
130
|
+
) {
|
|
131
|
+
// for batching separate og and comment/reply requests per credential
|
|
132
|
+
const documentsByTypeAndCredentials = documents.reduce(
|
|
133
|
+
(retObj, document, index) => {
|
|
134
|
+
const paramArray =
|
|
135
|
+
paramsByType[document.metaData.discussionType];
|
|
136
|
+
if (paramArray) {
|
|
137
|
+
paramArray.forEach((paramString) => {
|
|
138
|
+
let discussionTypeBucket =
|
|
139
|
+
retObj[
|
|
140
|
+
`${paramString}${document.systemData.connectionsCredential}`
|
|
141
|
+
];
|
|
142
|
+
if (!discussionTypeBucket) {
|
|
143
|
+
discussionTypeBucket = {
|
|
144
|
+
paramString,
|
|
145
|
+
document,
|
|
146
|
+
externalIds: [],
|
|
147
|
+
externalIdToDocumentIndex: {},
|
|
148
|
+
};
|
|
149
|
+
retObj[
|
|
150
|
+
`${paramString}${document.systemData.connectionsCredential}`
|
|
151
|
+
] = discussionTypeBucket;
|
|
152
|
+
}
|
|
153
|
+
const externalIdTrimmed = removePrefix(
|
|
154
|
+
// javascript weirdness to get around '.' notation
|
|
155
|
+
externalIdFrom
|
|
156
|
+
.split('.')
|
|
157
|
+
.reduce((a, b) => a[b], document)
|
|
158
|
+
);
|
|
159
|
+
discussionTypeBucket.externalIds.push(
|
|
160
|
+
externalIdTrimmed
|
|
161
|
+
);
|
|
162
|
+
discussionTypeBucket.externalIdToDocumentIndex[
|
|
163
|
+
externalIdTrimmed
|
|
164
|
+
] = index;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return retObj;
|
|
168
|
+
},
|
|
169
|
+
{}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const sortedResults = Array(documents.length).fill();
|
|
173
|
+
await Promise.all(
|
|
174
|
+
Object.values(documentsByTypeAndCredentials).map(
|
|
175
|
+
async (documentsByTypeAndCredential) => {
|
|
176
|
+
if (documentsByTypeAndCredential.externalIds.length) {
|
|
177
|
+
const {
|
|
178
|
+
metaData: {
|
|
179
|
+
inReplyTo: { profileId: business_id },
|
|
180
|
+
},
|
|
181
|
+
} = documentsByTypeAndCredential.document;
|
|
182
|
+
const trimmedBusinessId = removePrefix(business_id);
|
|
183
|
+
const requestResult = await this.sendRequest({
|
|
184
|
+
paramString: `${
|
|
185
|
+
documentsByTypeAndCredential.paramString
|
|
186
|
+
}?business_id=${trimmedBusinessId}&fields=["item_id","create_time","thumbnail_url","share_url","embed_url","caption","video_views","likes","comments","shares"]&filters=${JSON.stringify(
|
|
187
|
+
{
|
|
188
|
+
video_ids:
|
|
189
|
+
documentsByTypeAndCredential.externalIds,
|
|
190
|
+
}
|
|
191
|
+
)}`,
|
|
192
|
+
document: documentsByTypeAndCredential.document,
|
|
193
|
+
});
|
|
194
|
+
requestResult.data.videos.forEach(
|
|
195
|
+
({
|
|
196
|
+
item_id,
|
|
197
|
+
create_time,
|
|
198
|
+
thumbnail_url,
|
|
199
|
+
share_url,
|
|
200
|
+
embed_url,
|
|
201
|
+
caption,
|
|
202
|
+
video_views,
|
|
203
|
+
likes,
|
|
204
|
+
comments,
|
|
205
|
+
shares,
|
|
206
|
+
...args
|
|
207
|
+
}) => {
|
|
208
|
+
const documentIndex =
|
|
209
|
+
documentsByTypeAndCredential
|
|
210
|
+
.externalIdToDocumentIndex[item_id];
|
|
211
|
+
sortedResults[documentIndex] = {
|
|
212
|
+
...sortedResults[documentIndex],
|
|
213
|
+
item_id,
|
|
214
|
+
create_time,
|
|
215
|
+
thumbnail_url,
|
|
216
|
+
externalId:
|
|
217
|
+
documents[documentIndex].metaData
|
|
218
|
+
.externalId,
|
|
219
|
+
share_url,
|
|
220
|
+
embed_url,
|
|
221
|
+
caption,
|
|
222
|
+
video_views,
|
|
223
|
+
likes,
|
|
224
|
+
comments,
|
|
225
|
+
shares,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
);
|
|
233
|
+
return sortedResults;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async publish(message, videoId, markMessageAsCompleteDocumentId) {
|
|
237
|
+
const {
|
|
238
|
+
metaData: { discussionType },
|
|
239
|
+
} = message;
|
|
240
|
+
let publishedMessage;
|
|
241
|
+
|
|
242
|
+
switch (discussionType) {
|
|
243
|
+
case 'qt':
|
|
244
|
+
publishedMessage = await this.insertComment(message);
|
|
245
|
+
break;
|
|
246
|
+
case 're':
|
|
247
|
+
publishedMessage = await this.insertReply(
|
|
248
|
+
videoId,
|
|
249
|
+
message,
|
|
250
|
+
markMessageAsCompleteDocumentId
|
|
251
|
+
);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return publishedMessage;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async insertComment(document) {
|
|
259
|
+
const {
|
|
260
|
+
body: {
|
|
261
|
+
content: { text: text },
|
|
262
|
+
},
|
|
263
|
+
metaData: {
|
|
264
|
+
source: { id: business_id },
|
|
265
|
+
inReplyTo: { id: video_id },
|
|
266
|
+
},
|
|
267
|
+
} = document;
|
|
268
|
+
const { body: publishedMessage } =
|
|
269
|
+
(await this.sendPost({
|
|
270
|
+
paramString: 'comment/create/',
|
|
271
|
+
postData: {
|
|
272
|
+
business_id,
|
|
273
|
+
video_id,
|
|
274
|
+
text,
|
|
275
|
+
},
|
|
276
|
+
document,
|
|
277
|
+
})) || {};
|
|
278
|
+
return publishedMessage;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async insertReply(videoId, document, markMessageAsCompleteDocumentId) {
|
|
282
|
+
const {
|
|
283
|
+
body: {
|
|
284
|
+
content: { text: text },
|
|
285
|
+
},
|
|
286
|
+
metaData: {
|
|
287
|
+
inReplyTo: { id: comment_id, profileId: business_id },
|
|
288
|
+
},
|
|
289
|
+
} = document;
|
|
290
|
+
|
|
291
|
+
const { body: publishedMessage } =
|
|
292
|
+
(await this.sendPost({
|
|
293
|
+
paramString: 'comment/reply/create/',
|
|
294
|
+
postData: {
|
|
295
|
+
business_id: document.metaData.source.id,
|
|
296
|
+
comment_id: markMessageAsCompleteDocumentId || comment_id,
|
|
297
|
+
video_id: removePrefix(videoId),
|
|
298
|
+
text,
|
|
299
|
+
},
|
|
300
|
+
document,
|
|
301
|
+
})) || {};
|
|
302
|
+
|
|
303
|
+
return publishedMessage;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async toggleLike(document) {
|
|
307
|
+
const {
|
|
308
|
+
enrichments: {
|
|
309
|
+
socialScores: { tt_liked_by_user: likedByUser },
|
|
310
|
+
},
|
|
311
|
+
} = document;
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
let likeResponse = await (!likedByUser
|
|
315
|
+
? this.like(document)
|
|
316
|
+
: this.unlike(document));
|
|
317
|
+
return likeResponse;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.logger.error(`${document} - error recieved - ${error.code}`, error);
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async toggleHide(document) {
|
|
325
|
+
const {
|
|
326
|
+
documentId,
|
|
327
|
+
appData: { hidden: hiddenOnNative },
|
|
328
|
+
systemData: {
|
|
329
|
+
policies: { storage: { privateTo: companyId } = {} } = {},
|
|
330
|
+
} = {},
|
|
331
|
+
} = document;
|
|
332
|
+
let hideResponse;
|
|
333
|
+
let notificationPayload = document;
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
if (hiddenOnNative) {
|
|
337
|
+
hideResponse = await this.hide(document);
|
|
338
|
+
} else {
|
|
339
|
+
hideResponse = await this.unhide(document);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
notificationPayload.appData.hideStatus = {
|
|
343
|
+
code: hideResponse.success === true ? 200 : 500,
|
|
344
|
+
message:
|
|
345
|
+
hideResponse.success === true
|
|
346
|
+
? 'Success'
|
|
347
|
+
: 'Failed to Hide',
|
|
348
|
+
};
|
|
349
|
+
this.logger.debug(
|
|
350
|
+
`Notification payload for ${documentId} on company ${companyId} `,
|
|
351
|
+
notificationPayload
|
|
352
|
+
);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
this.logger.error(
|
|
355
|
+
`${documentId} - error recieved - ${error.code}`,
|
|
356
|
+
error
|
|
357
|
+
);
|
|
358
|
+
// message failed ot send, mark as such
|
|
359
|
+
notificationPayload.appData.hideStatus = {
|
|
360
|
+
code: 405,
|
|
361
|
+
message: 'Exception Occurred',
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
setTimeout(() => {
|
|
365
|
+
PushRepository.publish(
|
|
366
|
+
`${companyId}-${documentId}`,
|
|
367
|
+
notificationPayload
|
|
368
|
+
);
|
|
369
|
+
}, 5000);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async like(document) {
|
|
373
|
+
const {
|
|
374
|
+
metaData: {
|
|
375
|
+
externalId: comment_id,
|
|
376
|
+
source: { id: sourceId },
|
|
377
|
+
},
|
|
378
|
+
} = document;
|
|
379
|
+
const { body: publishedMessage } =
|
|
380
|
+
(await this.sendPost({
|
|
381
|
+
paramString: 'comment/like/',
|
|
382
|
+
postData: {
|
|
383
|
+
business_id: removePrefix(sourceId),
|
|
384
|
+
comment_id: removePrefix(comment_id),
|
|
385
|
+
action: 'LIKE',
|
|
386
|
+
},
|
|
387
|
+
document,
|
|
388
|
+
})) || {};
|
|
389
|
+
return publishedMessage;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async unlike(document) {
|
|
393
|
+
const {
|
|
394
|
+
metaData: {
|
|
395
|
+
inReplyTo: { id: comment_id },
|
|
396
|
+
source: { id: sourceId },
|
|
397
|
+
},
|
|
398
|
+
} = document;
|
|
399
|
+
const { body: publishedMessage } =
|
|
400
|
+
(await this.sendPost({
|
|
401
|
+
paramString: 'comment/like/',
|
|
402
|
+
postData: {
|
|
403
|
+
business_id: removePrefix(sourceId),
|
|
404
|
+
comment_id: removePrefix(comment_id),
|
|
405
|
+
action: 'unlike',
|
|
406
|
+
},
|
|
407
|
+
document,
|
|
408
|
+
})) || {};
|
|
409
|
+
return publishedMessage;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async hide(document) {
|
|
413
|
+
const {
|
|
414
|
+
metaData: {
|
|
415
|
+
inReplyTo: { id: comment_id, profileId: business_id },
|
|
416
|
+
},
|
|
417
|
+
} = document;
|
|
418
|
+
const { body: publishedMessage } =
|
|
419
|
+
(await this.sendPost({
|
|
420
|
+
paramString: 'comment/hide/',
|
|
421
|
+
postData: {
|
|
422
|
+
business_id,
|
|
423
|
+
comment_id,
|
|
424
|
+
action: 'HIDE',
|
|
425
|
+
},
|
|
426
|
+
document,
|
|
427
|
+
})) || {};
|
|
428
|
+
return publishedMessage;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async unhide(document) {
|
|
432
|
+
const {
|
|
433
|
+
metaData: {
|
|
434
|
+
inReplyTo: { id: comment_id, profileId: business_id },
|
|
435
|
+
},
|
|
436
|
+
} = document;
|
|
437
|
+
const { body: publishedMessage } =
|
|
438
|
+
(await this.sendPost({
|
|
439
|
+
paramString: 'comment/hide/',
|
|
440
|
+
postData: {
|
|
441
|
+
business_id,
|
|
442
|
+
comment_id,
|
|
443
|
+
action: 'UNHIDE',
|
|
444
|
+
},
|
|
445
|
+
document,
|
|
446
|
+
})) || {};
|
|
447
|
+
return publishedMessage;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async deleteComment(document) {
|
|
451
|
+
const {
|
|
452
|
+
metaData: {
|
|
453
|
+
inReplyTo: { id: comment_id, profileId: business_id },
|
|
454
|
+
},
|
|
455
|
+
} = document;
|
|
456
|
+
const { body: publishedMessage } =
|
|
457
|
+
(await this.sendPost({
|
|
458
|
+
paramString: 'comment/delete/',
|
|
459
|
+
postData: {
|
|
460
|
+
business_id,
|
|
461
|
+
comment_id,
|
|
462
|
+
},
|
|
463
|
+
document,
|
|
464
|
+
})) || {};
|
|
465
|
+
return publishedMessage;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async getProfile(business_id, document) {
|
|
469
|
+
const fields = '["username", "display_name", "profile_image"]';
|
|
470
|
+
const profile =
|
|
471
|
+
(await this.sendRequest({
|
|
472
|
+
paramString: `get/?business_id=${business_id}&fields=${fields}`,
|
|
473
|
+
document,
|
|
474
|
+
})) || {};
|
|
475
|
+
return profile;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async getPostData(business_id, video_id, document) {
|
|
479
|
+
const fields =
|
|
480
|
+
'["item_id", "thumbnail_url", "create_time", "username", "display_name", "profile_image"]';
|
|
481
|
+
const video_ids = `["${video_id}"]`;
|
|
482
|
+
const profile =
|
|
483
|
+
(await this.sendRequest({
|
|
484
|
+
paramString: `video/list?business_id=${business_id}&fields=${fields}&filters={"video_ids":${video_ids}}`,
|
|
485
|
+
document,
|
|
486
|
+
})) || {};
|
|
487
|
+
return profile;
|
|
488
|
+
}
|
|
489
|
+
}
|
package/src/data-access/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { InstagramApiClient } from './http/instagramApi.client.js';
|
|
|
10
10
|
import { InstagramVideoClient } from './http/InstagramVideoClient.js';
|
|
11
11
|
import { IRClient } from './http/ir.client.js';
|
|
12
12
|
import { LinkedInApiClient } from './http/linkedInApi.client.js';
|
|
13
|
+
import { TikTokApiClient } from './http/tiktokApi.client.js';
|
|
13
14
|
import { MasfClient } from './http/masf.client.js';
|
|
14
15
|
import { WarpZoneApiClient } from './http/WarpZoneApi.client.js';
|
|
15
16
|
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
InstagramVideoClient,
|
|
27
28
|
IRClient,
|
|
28
29
|
LinkedInApiClient,
|
|
30
|
+
TikTokApiClient,
|
|
29
31
|
MasfClient,
|
|
30
32
|
WarpZoneApiClient,
|
|
31
33
|
};
|