@meltwater/conversations-api-services 1.1.4 → 1.1.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/dist/cjs/data-access/http/threads.native.js +124 -65
- package/dist/cjs/lib/externalId.helpers.js +1 -1
- package/dist/esm/data-access/http/threads.native.js +120 -64
- package/dist/esm/lib/externalId.helpers.js +1 -1
- package/package.json +1 -1
- package/src/data-access/http/threads.native.js +173 -119
- package/src/lib/externalId.helpers.js +1 -1
|
@@ -3,90 +3,149 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.getInsights = getInsights;
|
|
7
|
+
exports.getProfile = getProfile;
|
|
8
|
+
exports.reply = reply;
|
|
9
|
+
exports.repost = repost;
|
|
7
10
|
var _superagent = _interopRequireDefault(require("superagent"));
|
|
8
11
|
var _externalIdHelpers = require("../../lib/externalId.helpers.js");
|
|
9
12
|
var _loggerHelpers = require("../../lib/logger.helpers.js");
|
|
10
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
14
|
const THREADS_URL = 'https://graph.threads.net/v1.0';
|
|
12
15
|
const THREADS_PUBLISH_URL = `${THREADS_URL}/me/threads_publish`;
|
|
16
|
+
const MAX_RETRY_COUNT = 11;
|
|
17
|
+
const SHORT_WAIT_TIME_MS = 10000; // 10 seconds
|
|
18
|
+
const LONG_WAIT_TIME_MS = 60000; // 60 seconds
|
|
19
|
+
|
|
13
20
|
async function confirmCreationId(accessToken, creationId, logger) {
|
|
14
|
-
let retry =
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
for (let retry = 0; retry <= MAX_RETRY_COUNT; retry++) {
|
|
22
|
+
const response = await _superagent.default.get(`${THREADS_URL}/${creationId}`).set('Accept', 'application/json').set('Content-Type', 'application/json').query({
|
|
23
|
+
access_token: accessToken,
|
|
24
|
+
fields: 'id,status,error_message'
|
|
25
|
+
}).send();
|
|
26
|
+
if (response.body.status === 'IN_PROGRESS') {
|
|
27
|
+
const waitTime = retry === 0 ? SHORT_WAIT_TIME_MS : LONG_WAIT_TIME_MS;
|
|
28
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Creation ID ${creationId} in progress. Retry ${retry}. Waiting ${waitTime / 1000} seconds.`);
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
30
|
+
} else {
|
|
31
|
+
(0, _loggerHelpers.loggerDebug)(logger, 'Threads Response status', response.status);
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const error = new Error(`Creation ID ${creationId} is taking too long.`);
|
|
36
|
+
error.code = 408; // Request Timeout
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
async function sendRequest(apiUrl, params, logger) {
|
|
40
|
+
try {
|
|
41
|
+
const response = await _superagent.default.get(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
42
|
+
if (response.status !== 200) {
|
|
43
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
(0, _loggerHelpers.loggerDebug)(logger, 'Threads Response status', response.status);
|
|
46
|
+
return response;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const errorMessage = err?.response?.body?.error?.message || err.message;
|
|
49
|
+
(0, _loggerHelpers.loggerError)(logger, `Failed to call Threads API: ${errorMessage}`, err);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function requestApi(apiUrl, params, logger) {
|
|
54
|
+
try {
|
|
55
|
+
const response = await _superagent.default.post(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
56
|
+
if (response.status !== 200) {
|
|
57
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
58
|
+
}
|
|
59
|
+
(0, _loggerHelpers.loggerDebug)(logger, 'Threads Response status', response.status);
|
|
60
|
+
return response;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const errorMessage = err?.response?.body?.error?.message || err.message;
|
|
63
|
+
(0, _loggerHelpers.loggerError)(logger, `Failed to call Threads API: ${errorMessage}`, err);
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
68
|
+
const query = await constructReplyQuery(accessToken, inReplyToId, text, asset, logger);
|
|
69
|
+
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
70
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API Publish reply Response`, {
|
|
71
|
+
responseBody: JSON.stringify(response.body)
|
|
72
|
+
});
|
|
73
|
+
return response.body;
|
|
74
|
+
}
|
|
75
|
+
async function repost(token, externalId, logger) {
|
|
76
|
+
const postId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
77
|
+
let response = await requestApi(`${THREADS_URL}/${postId}/repost`, {
|
|
78
|
+
access_token: token
|
|
79
|
+
}, logger);
|
|
80
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API repost Response`, {
|
|
81
|
+
responseBody: JSON.stringify(response.body)
|
|
82
|
+
});
|
|
83
|
+
return response.body;
|
|
84
|
+
}
|
|
85
|
+
async function getProfile(token, externalId, fields, logger) {
|
|
86
|
+
const userId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
87
|
+
let response = await sendRequest(`${THREADS_URL}/${userId}?fields=${fields}`, {
|
|
88
|
+
access_token: token
|
|
89
|
+
}, logger);
|
|
90
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API getProfile Response`, {
|
|
91
|
+
responseBody: JSON.stringify(response.body)
|
|
92
|
+
});
|
|
93
|
+
return response.body;
|
|
94
|
+
}
|
|
95
|
+
async function getInsights(token, externalId, metric, logger) {
|
|
96
|
+
const mediaId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
97
|
+
let response = await sendRequest(`${THREADS_URL}/${mediaId}/insights`, {
|
|
98
|
+
access_token: token,
|
|
99
|
+
metric
|
|
100
|
+
}, logger);
|
|
101
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API getInsights Response`, {
|
|
102
|
+
responseBody: JSON.stringify(response.body)
|
|
103
|
+
});
|
|
104
|
+
return response.body;
|
|
105
|
+
}
|
|
106
|
+
async function constructReplyQuery(accessToken, inReplyToId, text, asset, logger) {
|
|
107
|
+
const baseQuery = {
|
|
17
108
|
access_token: accessToken,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
109
|
+
reply_to_id: inReplyToId,
|
|
110
|
+
text
|
|
111
|
+
};
|
|
112
|
+
const mediaQuery = getMediaQuery(asset);
|
|
113
|
+
const query = {
|
|
114
|
+
...baseQuery,
|
|
115
|
+
...mediaQuery
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const containerResponse = await _superagent.default.post(`${THREADS_URL}/me/threads`).set('Accept', 'application/json').set('Content-Type', 'application/json').query(query).send();
|
|
119
|
+
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
120
|
+
return {
|
|
121
|
+
...query,
|
|
122
|
+
containerResponse
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
(0, _loggerHelpers.loggerError)(logger, 'Error constructing reply query', error);
|
|
28
126
|
throw error;
|
|
29
127
|
}
|
|
30
|
-
(0, _loggerHelpers.loggerDebug)(logger, 'Threads Response status', response.status);
|
|
31
|
-
return response;
|
|
32
128
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
access_token: accessToken,
|
|
38
|
-
reply_to_id: inReplyToId,
|
|
39
|
-
text
|
|
129
|
+
function getMediaQuery(asset) {
|
|
130
|
+
if (!asset) {
|
|
131
|
+
return {
|
|
132
|
+
media_type: 'TEXT'
|
|
40
133
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
46
|
-
} else if (asset.type === 'image') {
|
|
47
|
-
query = {
|
|
48
|
-
...query,
|
|
134
|
+
}
|
|
135
|
+
switch (asset.type) {
|
|
136
|
+
case 'image':
|
|
137
|
+
return {
|
|
49
138
|
media_type: 'IMAGE',
|
|
50
139
|
image_url: asset.url,
|
|
51
140
|
alt_text: asset.altText ?? ''
|
|
52
141
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
...query,
|
|
142
|
+
case 'video':
|
|
143
|
+
return {
|
|
56
144
|
media_type: 'VIDEO',
|
|
57
145
|
video_url: asset.url,
|
|
58
146
|
alt_text: asset.altText ?? ''
|
|
59
147
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
63
|
-
response = await _superagent.default.post(THREADS_PUBLISH_URL).set('Accept', 'application/json').set('Content-Type', 'application/json').query({
|
|
64
|
-
access_token: accessToken,
|
|
65
|
-
creation_id: containerResponse.body.id
|
|
66
|
-
}).send();
|
|
67
|
-
} catch (err) {
|
|
68
|
-
if (err && err.response && err.response.body && err.response.body.error) {
|
|
69
|
-
(0, _loggerHelpers.loggerError)(logger, `Failed to call threads api: ${err.response.body.error.message}`);
|
|
70
|
-
} else {
|
|
71
|
-
(0, _loggerHelpers.loggerError)(logger, `Failed to call threads api`, err);
|
|
72
|
-
}
|
|
73
|
-
throw err;
|
|
74
|
-
}
|
|
75
|
-
if (response.status !== 200) {
|
|
76
|
-
(0, _loggerHelpers.loggerError)(logger, `Failed to call threads api`, {
|
|
77
|
-
responseBody: JSON.stringify(response.body)
|
|
78
|
-
});
|
|
79
|
-
let error = new Error(`Failed to call threads api`);
|
|
80
|
-
error.code = response.status;
|
|
81
|
-
throw error;
|
|
148
|
+
default:
|
|
149
|
+
throw new Error(`Unsupported asset type: ${asset.type}`);
|
|
82
150
|
}
|
|
83
|
-
(0, _loggerHelpers.loggerDebug)(logger, 'Threads Response status', response.status);
|
|
84
|
-
return response;
|
|
85
|
-
}
|
|
86
|
-
async function comment(accessToken, inReplyToId, text, asset, logger) {
|
|
87
|
-
let response = await requestApi(`${THREADS_URL}/me/threads`, accessToken, (0, _externalIdHelpers.removePrefix)(inReplyToId), text, asset, logger);
|
|
88
|
-
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API Publish Comment Response`, {
|
|
89
|
-
responseBody: JSON.stringify(response.body)
|
|
90
|
-
});
|
|
91
|
-
return response.body;
|
|
92
151
|
}
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.removePrefix = removePrefix;
|
|
7
7
|
exports.twitterPrefixCheck = twitterPrefixCheck;
|
|
8
8
|
function removePrefix(document) {
|
|
9
|
-
return document && document.replace(/id\:[a-zA-Z]+\.com:/g, '');
|
|
9
|
+
return document && document.replace(/id\:[a-zA-Z]+\.(com|net):/g, '');
|
|
10
10
|
}
|
|
11
11
|
function twitterPrefixCheck(socialOriginType, value) {
|
|
12
12
|
let result = value;
|
|
@@ -3,83 +3,139 @@ import { removePrefix } from '../../lib/externalId.helpers.js';
|
|
|
3
3
|
import { loggerDebug, loggerError, loggerInfo } from '../../lib/logger.helpers.js';
|
|
4
4
|
const THREADS_URL = 'https://graph.threads.net/v1.0';
|
|
5
5
|
const THREADS_PUBLISH_URL = `${THREADS_URL}/me/threads_publish`;
|
|
6
|
+
const MAX_RETRY_COUNT = 11;
|
|
7
|
+
const SHORT_WAIT_TIME_MS = 10000; // 10 seconds
|
|
8
|
+
const LONG_WAIT_TIME_MS = 60000; // 60 seconds
|
|
9
|
+
|
|
6
10
|
async function confirmCreationId(accessToken, creationId, logger) {
|
|
7
|
-
let retry =
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
for (let retry = 0; retry <= MAX_RETRY_COUNT; retry++) {
|
|
12
|
+
const response = await superagent.get(`${THREADS_URL}/${creationId}`).set('Accept', 'application/json').set('Content-Type', 'application/json').query({
|
|
13
|
+
access_token: accessToken,
|
|
14
|
+
fields: 'id,status,error_message'
|
|
15
|
+
}).send();
|
|
16
|
+
if (response.body.status === 'IN_PROGRESS') {
|
|
17
|
+
const waitTime = retry === 0 ? SHORT_WAIT_TIME_MS : LONG_WAIT_TIME_MS;
|
|
18
|
+
loggerInfo(logger, `Creation ID ${creationId} in progress. Retry ${retry}. Waiting ${waitTime / 1000} seconds.`);
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
20
|
+
} else {
|
|
21
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const error = new Error(`Creation ID ${creationId} is taking too long.`);
|
|
26
|
+
error.code = 408; // Request Timeout
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
async function sendRequest(apiUrl, params, logger) {
|
|
30
|
+
try {
|
|
31
|
+
const response = await superagent.get(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
32
|
+
if (response.status !== 200) {
|
|
33
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
36
|
+
return response;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const errorMessage = err?.response?.body?.error?.message || err.message;
|
|
39
|
+
loggerError(logger, `Failed to call Threads API: ${errorMessage}`, err);
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function requestApi(apiUrl, params, logger) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await superagent.post(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
46
|
+
if (response.status !== 200) {
|
|
47
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
50
|
+
return response;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
const errorMessage = err?.response?.body?.error?.message || err.message;
|
|
53
|
+
loggerError(logger, `Failed to call Threads API: ${errorMessage}`, err);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
58
|
+
const query = await constructReplyQuery(accessToken, inReplyToId, text, asset, logger);
|
|
59
|
+
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
60
|
+
loggerInfo(logger, `Native Threads API Publish reply Response`, {
|
|
61
|
+
responseBody: JSON.stringify(response.body)
|
|
62
|
+
});
|
|
63
|
+
return response.body;
|
|
64
|
+
}
|
|
65
|
+
export async function repost(token, externalId, logger) {
|
|
66
|
+
const postId = removePrefix(externalId);
|
|
67
|
+
let response = await requestApi(`${THREADS_URL}/${postId}/repost`, {
|
|
68
|
+
access_token: token
|
|
69
|
+
}, logger);
|
|
70
|
+
loggerInfo(logger, `Native Threads API repost Response`, {
|
|
71
|
+
responseBody: JSON.stringify(response.body)
|
|
72
|
+
});
|
|
73
|
+
return response.body;
|
|
74
|
+
}
|
|
75
|
+
export async function getProfile(token, externalId, fields, logger) {
|
|
76
|
+
const userId = removePrefix(externalId);
|
|
77
|
+
let response = await sendRequest(`${THREADS_URL}/${userId}?fields=${fields}`, {
|
|
78
|
+
access_token: token
|
|
79
|
+
}, logger);
|
|
80
|
+
loggerInfo(logger, `Native Threads API getProfile Response`, {
|
|
81
|
+
responseBody: JSON.stringify(response.body)
|
|
82
|
+
});
|
|
83
|
+
return response.body;
|
|
84
|
+
}
|
|
85
|
+
export async function getInsights(token, externalId, metric, logger) {
|
|
86
|
+
const mediaId = removePrefix(externalId);
|
|
87
|
+
let response = await sendRequest(`${THREADS_URL}/${mediaId}/insights`, {
|
|
88
|
+
access_token: token,
|
|
89
|
+
metric
|
|
90
|
+
}, logger);
|
|
91
|
+
loggerInfo(logger, `Native Threads API getInsights Response`, {
|
|
92
|
+
responseBody: JSON.stringify(response.body)
|
|
93
|
+
});
|
|
94
|
+
return response.body;
|
|
95
|
+
}
|
|
96
|
+
async function constructReplyQuery(accessToken, inReplyToId, text, asset, logger) {
|
|
97
|
+
const baseQuery = {
|
|
10
98
|
access_token: accessToken,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
99
|
+
reply_to_id: inReplyToId,
|
|
100
|
+
text
|
|
101
|
+
};
|
|
102
|
+
const mediaQuery = getMediaQuery(asset);
|
|
103
|
+
const query = {
|
|
104
|
+
...baseQuery,
|
|
105
|
+
...mediaQuery
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
const containerResponse = await superagent.post(`${THREADS_URL}/me/threads`).set('Accept', 'application/json').set('Content-Type', 'application/json').query(query).send();
|
|
109
|
+
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
110
|
+
return {
|
|
111
|
+
...query,
|
|
112
|
+
containerResponse
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
loggerError(logger, 'Error constructing reply query', error);
|
|
21
116
|
throw error;
|
|
22
117
|
}
|
|
23
|
-
loggerDebug(logger, 'Threads Response status', response.status);
|
|
24
|
-
return response;
|
|
25
118
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
access_token: accessToken,
|
|
31
|
-
reply_to_id: inReplyToId,
|
|
32
|
-
text
|
|
119
|
+
function getMediaQuery(asset) {
|
|
120
|
+
if (!asset) {
|
|
121
|
+
return {
|
|
122
|
+
media_type: 'TEXT'
|
|
33
123
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
} else if (asset.type === 'image') {
|
|
40
|
-
query = {
|
|
41
|
-
...query,
|
|
124
|
+
}
|
|
125
|
+
switch (asset.type) {
|
|
126
|
+
case 'image':
|
|
127
|
+
return {
|
|
42
128
|
media_type: 'IMAGE',
|
|
43
129
|
image_url: asset.url,
|
|
44
130
|
alt_text: asset.altText ?? ''
|
|
45
131
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
...query,
|
|
132
|
+
case 'video':
|
|
133
|
+
return {
|
|
49
134
|
media_type: 'VIDEO',
|
|
50
135
|
video_url: asset.url,
|
|
51
136
|
alt_text: asset.altText ?? ''
|
|
52
137
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
56
|
-
response = await superagent.post(THREADS_PUBLISH_URL).set('Accept', 'application/json').set('Content-Type', 'application/json').query({
|
|
57
|
-
access_token: accessToken,
|
|
58
|
-
creation_id: containerResponse.body.id
|
|
59
|
-
}).send();
|
|
60
|
-
} catch (err) {
|
|
61
|
-
if (err && err.response && err.response.body && err.response.body.error) {
|
|
62
|
-
loggerError(logger, `Failed to call threads api: ${err.response.body.error.message}`);
|
|
63
|
-
} else {
|
|
64
|
-
loggerError(logger, `Failed to call threads api`, err);
|
|
65
|
-
}
|
|
66
|
-
throw err;
|
|
138
|
+
default:
|
|
139
|
+
throw new Error(`Unsupported asset type: ${asset.type}`);
|
|
67
140
|
}
|
|
68
|
-
if (response.status !== 200) {
|
|
69
|
-
loggerError(logger, `Failed to call threads api`, {
|
|
70
|
-
responseBody: JSON.stringify(response.body)
|
|
71
|
-
});
|
|
72
|
-
let error = new Error(`Failed to call threads api`);
|
|
73
|
-
error.code = response.status;
|
|
74
|
-
throw error;
|
|
75
|
-
}
|
|
76
|
-
loggerDebug(logger, 'Threads Response status', response.status);
|
|
77
|
-
return response;
|
|
78
|
-
}
|
|
79
|
-
export async function comment(accessToken, inReplyToId, text, asset, logger) {
|
|
80
|
-
let response = await requestApi(`${THREADS_URL}/me/threads`, accessToken, removePrefix(inReplyToId), text, asset, logger);
|
|
81
|
-
loggerInfo(logger, `Native Threads API Publish Comment Response`, {
|
|
82
|
-
responseBody: JSON.stringify(response.body)
|
|
83
|
-
});
|
|
84
|
-
return response.body;
|
|
85
141
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function removePrefix(document) {
|
|
2
|
-
return document && document.replace(/id\:[a-zA-Z]+\.com:/g, '');
|
|
2
|
+
return document && document.replace(/id\:[a-zA-Z]+\.(com|net):/g, '');
|
|
3
3
|
}
|
|
4
4
|
export function twitterPrefixCheck(socialOriginType, value) {
|
|
5
5
|
let result = value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meltwater/conversations-api-services",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Repository to contain all conversations api services shared across our services",
|
|
5
5
|
"main": "dist/cjs/data-access/index.js",
|
|
6
6
|
"module": "dist/esm/data-access/index.js",
|
|
@@ -1,152 +1,206 @@
|
|
|
1
1
|
import superagent from 'superagent';
|
|
2
2
|
import { removePrefix } from '../../lib/externalId.helpers.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
loggerDebug,
|
|
5
|
+
loggerError,
|
|
6
|
+
loggerInfo,
|
|
7
|
+
} from '../../lib/logger.helpers.js';
|
|
5
8
|
|
|
6
9
|
const THREADS_URL = 'https://graph.threads.net/v1.0';
|
|
7
10
|
const THREADS_PUBLISH_URL = `${THREADS_URL}/me/threads_publish`;
|
|
8
11
|
|
|
12
|
+
const MAX_RETRY_COUNT = 11;
|
|
13
|
+
const SHORT_WAIT_TIME_MS = 10000; // 10 seconds
|
|
14
|
+
const LONG_WAIT_TIME_MS = 60000; // 60 seconds
|
|
9
15
|
|
|
10
|
-
async function confirmCreationId(accessToken, creationId, logger
|
|
11
|
-
let
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.send();
|
|
22
|
-
if(response.body.status == 'IN_PROGRESS' && retry < 11){
|
|
23
|
-
// small wait for first attempt, sometimes its just a text post that doesn't take long
|
|
24
|
-
loggerInfo(logger,
|
|
25
|
-
`Creation ID is in progress ${creationId} retry ${retry} Waiting ${retry ? 60 : 10} seconds`
|
|
26
|
-
);
|
|
27
|
-
await new Promise((resolve) => setTimeout(resolve, retry ? 60000 : 10000));
|
|
28
|
-
return await confirmCreationId(accessToken, creationId, logger, retry + 1);
|
|
29
|
-
}else if(response.body.status == 'IN_PROGRESS' && retry == 11){
|
|
30
|
-
let error = new Error(
|
|
31
|
-
`Creation ID is in progress BUT TAKING TOO LONG ${creationId}`
|
|
32
|
-
);
|
|
33
|
-
error.code = response.status;
|
|
34
|
-
throw error
|
|
35
|
-
}
|
|
16
|
+
async function confirmCreationId(accessToken, creationId, logger) {
|
|
17
|
+
for (let retry = 0; retry <= MAX_RETRY_COUNT; retry++) {
|
|
18
|
+
const response = await superagent
|
|
19
|
+
.get(`${THREADS_URL}/${creationId}`)
|
|
20
|
+
.set('Accept', 'application/json')
|
|
21
|
+
.set('Content-Type', 'application/json')
|
|
22
|
+
.query({
|
|
23
|
+
access_token: accessToken,
|
|
24
|
+
fields: 'id,status,error_message',
|
|
25
|
+
})
|
|
26
|
+
.send();
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
if (response.body.status === 'IN_PROGRESS') {
|
|
29
|
+
const waitTime =
|
|
30
|
+
retry === 0 ? SHORT_WAIT_TIME_MS : LONG_WAIT_TIME_MS;
|
|
31
|
+
loggerInfo(
|
|
32
|
+
logger,
|
|
33
|
+
`Creation ID ${creationId} in progress. Retry ${retry}. Waiting ${
|
|
34
|
+
waitTime / 1000
|
|
35
|
+
} seconds.`
|
|
36
|
+
);
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
38
|
+
} else {
|
|
39
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
40
|
+
return response;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
39
43
|
|
|
40
|
-
|
|
44
|
+
const error = new Error(`Creation ID ${creationId} is taking too long.`);
|
|
45
|
+
error.code = 408; // Request Timeout
|
|
46
|
+
throw error;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
async function requestApi(
|
|
45
|
-
apiUrl,
|
|
46
|
-
accessToken,
|
|
47
|
-
inReplyToId,
|
|
48
|
-
text,
|
|
49
|
-
asset,
|
|
50
|
-
logger
|
|
51
|
-
) {
|
|
52
|
-
let response = {};
|
|
53
|
-
|
|
49
|
+
async function sendRequest(apiUrl, params, logger) {
|
|
54
50
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
reply_to_id: inReplyToId,
|
|
58
|
-
text,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
if (!asset) {
|
|
62
|
-
query = {
|
|
63
|
-
...query,
|
|
64
|
-
media_type: 'TEXT',
|
|
65
|
-
};
|
|
66
|
-
} else if (asset.type === 'image') {
|
|
67
|
-
query = {
|
|
68
|
-
...query,
|
|
69
|
-
media_type: 'IMAGE',
|
|
70
|
-
image_url: asset.url,
|
|
71
|
-
alt_text: asset.altText ?? '',
|
|
72
|
-
}
|
|
73
|
-
} else if (asset.type === 'video') {
|
|
74
|
-
query = {
|
|
75
|
-
...query,
|
|
76
|
-
media_type: 'VIDEO',
|
|
77
|
-
video_url: asset.url,
|
|
78
|
-
alt_text: asset.altText ?? '',
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const containerResponse = await superagent
|
|
83
|
-
.post(apiUrl)
|
|
51
|
+
const response = await superagent
|
|
52
|
+
.get(apiUrl)
|
|
84
53
|
.set('Accept', 'application/json')
|
|
85
54
|
.set('Content-Type', 'application/json')
|
|
86
|
-
.query(
|
|
55
|
+
.query(params)
|
|
87
56
|
.send();
|
|
88
|
-
|
|
89
|
-
response
|
|
90
|
-
.
|
|
57
|
+
|
|
58
|
+
if (response.status !== 200) {
|
|
59
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
63
|
+
return response;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
const errorMessage = err?.response?.body?.error?.message || err.message;
|
|
66
|
+
loggerError(logger, `Failed to call Threads API: ${errorMessage}`, err);
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function requestApi(apiUrl, params, logger) {
|
|
72
|
+
try {
|
|
73
|
+
const response = await superagent
|
|
74
|
+
.post(apiUrl)
|
|
91
75
|
.set('Accept', 'application/json')
|
|
92
76
|
.set('Content-Type', 'application/json')
|
|
93
|
-
.query(
|
|
94
|
-
access_token: accessToken,
|
|
95
|
-
creation_id: containerResponse.body.id,
|
|
96
|
-
})
|
|
77
|
+
.query(params)
|
|
97
78
|
.send();
|
|
98
79
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
err &&
|
|
102
|
-
err.response &&
|
|
103
|
-
err.response.body &&
|
|
104
|
-
err.response.body.error
|
|
105
|
-
) {
|
|
106
|
-
loggerError(logger,
|
|
107
|
-
`Failed to call threads api: ${err.response.body.error.message}`
|
|
108
|
-
);
|
|
109
|
-
} else {
|
|
110
|
-
loggerError(logger,
|
|
111
|
-
`Failed to call threads api`,
|
|
112
|
-
err
|
|
113
|
-
);
|
|
80
|
+
if (response.status !== 200) {
|
|
81
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
114
82
|
}
|
|
115
|
-
throw err;
|
|
116
|
-
}
|
|
117
83
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
`Failed to call threads api`
|
|
125
|
-
);
|
|
126
|
-
error.code = response.status;
|
|
127
|
-
throw error;
|
|
84
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
85
|
+
return response;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const errorMessage = err?.response?.body?.error?.message || err.message;
|
|
88
|
+
loggerError(logger, `Failed to call Threads API: ${errorMessage}`, err);
|
|
89
|
+
throw err;
|
|
128
90
|
}
|
|
129
|
-
|
|
130
|
-
loggerDebug(logger,'Threads Response status', response.status);
|
|
131
|
-
|
|
132
|
-
return response;
|
|
133
91
|
}
|
|
134
92
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
let response = await requestApi(
|
|
138
|
-
`${THREADS_URL}/me/threads`,
|
|
93
|
+
export async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
94
|
+
const query = await constructReplyQuery(
|
|
139
95
|
accessToken,
|
|
140
|
-
|
|
96
|
+
inReplyToId,
|
|
141
97
|
text,
|
|
142
98
|
asset,
|
|
143
99
|
logger
|
|
144
100
|
);
|
|
101
|
+
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
102
|
+
|
|
103
|
+
loggerInfo(logger, `Native Threads API Publish reply Response`, {
|
|
104
|
+
responseBody: JSON.stringify(response.body),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return response.body;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function repost(token, externalId, logger) {
|
|
111
|
+
const postId = removePrefix(externalId);
|
|
112
|
+
let response = await requestApi(
|
|
113
|
+
`${THREADS_URL}/${postId}/repost`,
|
|
114
|
+
{ access_token: token },
|
|
115
|
+
logger
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
loggerInfo(logger, `Native Threads API repost Response`, {
|
|
119
|
+
responseBody: JSON.stringify(response.body),
|
|
120
|
+
});
|
|
121
|
+
return response.body;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function getProfile(token, externalId, fields, logger) {
|
|
125
|
+
const userId = removePrefix(externalId);
|
|
126
|
+
let response = await sendRequest(
|
|
127
|
+
`${THREADS_URL}/${userId}?fields=${fields}`,
|
|
128
|
+
{ access_token: token },
|
|
129
|
+
logger
|
|
130
|
+
);
|
|
145
131
|
|
|
146
|
-
loggerInfo(logger,
|
|
147
|
-
|
|
148
|
-
|
|
132
|
+
loggerInfo(logger, `Native Threads API getProfile Response`, {
|
|
133
|
+
responseBody: JSON.stringify(response.body),
|
|
134
|
+
});
|
|
135
|
+
return response.body;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function getInsights(token, externalId, metric, logger) {
|
|
139
|
+
const mediaId = removePrefix(externalId);
|
|
140
|
+
let response = await sendRequest(
|
|
141
|
+
`${THREADS_URL}/${mediaId}/insights`,
|
|
142
|
+
{ access_token: token, metric },
|
|
143
|
+
logger
|
|
149
144
|
);
|
|
150
145
|
|
|
146
|
+
loggerInfo(logger, `Native Threads API getInsights Response`, {
|
|
147
|
+
responseBody: JSON.stringify(response.body),
|
|
148
|
+
});
|
|
151
149
|
return response.body;
|
|
152
|
-
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function constructReplyQuery(
|
|
153
|
+
accessToken,
|
|
154
|
+
inReplyToId,
|
|
155
|
+
text,
|
|
156
|
+
asset,
|
|
157
|
+
logger
|
|
158
|
+
) {
|
|
159
|
+
const baseQuery = {
|
|
160
|
+
access_token: accessToken,
|
|
161
|
+
reply_to_id: inReplyToId,
|
|
162
|
+
text,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const mediaQuery = getMediaQuery(asset);
|
|
166
|
+
const query = { ...baseQuery, ...mediaQuery };
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const containerResponse = await superagent
|
|
170
|
+
.post(`${THREADS_URL}/me/threads`)
|
|
171
|
+
.set('Accept', 'application/json')
|
|
172
|
+
.set('Content-Type', 'application/json')
|
|
173
|
+
.query(query)
|
|
174
|
+
.send();
|
|
175
|
+
|
|
176
|
+
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
177
|
+
|
|
178
|
+
return { ...query, containerResponse };
|
|
179
|
+
} catch (error) {
|
|
180
|
+
loggerError(logger, 'Error constructing reply query', error);
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getMediaQuery(asset) {
|
|
186
|
+
if (!asset) {
|
|
187
|
+
return { media_type: 'TEXT' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
switch (asset.type) {
|
|
191
|
+
case 'image':
|
|
192
|
+
return {
|
|
193
|
+
media_type: 'IMAGE',
|
|
194
|
+
image_url: asset.url,
|
|
195
|
+
alt_text: asset.altText ?? '',
|
|
196
|
+
};
|
|
197
|
+
case 'video':
|
|
198
|
+
return {
|
|
199
|
+
media_type: 'VIDEO',
|
|
200
|
+
video_url: asset.url,
|
|
201
|
+
alt_text: asset.altText ?? '',
|
|
202
|
+
};
|
|
203
|
+
default:
|
|
204
|
+
throw new Error(`Unsupported asset type: ${asset.type}`);
|
|
205
|
+
}
|
|
206
|
+
}
|