@meltwater/conversations-api-services 1.1.5 → 1.1.7
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.
|
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.getInsights = getInsights;
|
|
7
7
|
exports.getProfile = getProfile;
|
|
8
|
+
exports.manageReply = manageReply;
|
|
9
|
+
exports.quote = quote;
|
|
8
10
|
exports.reply = reply;
|
|
9
11
|
exports.repost = repost;
|
|
10
12
|
var _superagent = _interopRequireDefault(require("superagent"));
|
|
@@ -17,25 +19,68 @@ const MAX_RETRY_COUNT = 11;
|
|
|
17
19
|
const SHORT_WAIT_TIME_MS = 10000; // 10 seconds
|
|
18
20
|
const LONG_WAIT_TIME_MS = 60000; // 60 seconds
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Utility function to retry an asynchronous operation with exponential backoff.
|
|
24
|
+
* @param {Function} operation - The async operation to retry.
|
|
25
|
+
* @param {number} maxRetries - Maximum number of retries.
|
|
26
|
+
* @param {number} initialWaitTime - Initial wait time in milliseconds.
|
|
27
|
+
* @param {Function} logger - Logger instance for logging retries.
|
|
28
|
+
* @returns {Promise<any>} The result of the operation.
|
|
29
|
+
* @throws {Error} If the operation fails after all retries.
|
|
30
|
+
*/
|
|
31
|
+
async function retryWithBackoff(operation, maxRetries, initialWaitTime, logger) {
|
|
32
|
+
let waitTime = initialWaitTime;
|
|
33
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
34
|
+
try {
|
|
35
|
+
return await operation();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (attempt === maxRetries) {
|
|
38
|
+
throw error; // Rethrow error if max retries are reached
|
|
39
|
+
}
|
|
40
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Retry attempt ${attempt + 1} failed. Retrying in ${waitTime / 1000} seconds...`);
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
42
|
+
waitTime *= 2; // Exponential backoff
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Confirms the creation ID by polling the Threads API until the status is no longer "IN_PROGRESS".
|
|
49
|
+
* @param {string} accessToken - The access token for authentication.
|
|
50
|
+
* @param {string} creationId - The ID of the creation process to confirm.
|
|
51
|
+
* @param {Object} logger - Logger instance for logging.
|
|
52
|
+
* @returns {Promise<Object>} The API response when the status is no longer "IN_PROGRESS".
|
|
53
|
+
* @throws {Error} If the status remains "IN_PROGRESS" after all retries.
|
|
54
|
+
*/
|
|
20
55
|
async function confirmCreationId(accessToken, creationId, logger) {
|
|
21
|
-
|
|
56
|
+
const operation = async () => {
|
|
22
57
|
const response = await _superagent.default.get(`${THREADS_URL}/${creationId}`).set('Accept', 'application/json').set('Content-Type', 'application/json').query({
|
|
23
58
|
access_token: accessToken,
|
|
24
59
|
fields: 'id,status,error_message'
|
|
25
60
|
}).send();
|
|
26
61
|
if (response.body.status === 'IN_PROGRESS') {
|
|
27
|
-
|
|
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;
|
|
62
|
+
throw new Error('Status is still IN_PROGRESS');
|
|
33
63
|
}
|
|
64
|
+
(0, _loggerHelpers.loggerDebug)(logger, 'Threads Response status', response.status);
|
|
65
|
+
return response;
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
return await retryWithBackoff(operation, MAX_RETRY_COUNT, SHORT_WAIT_TIME_MS, logger);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
(0, _loggerHelpers.loggerError)(logger, `Creation ID ${creationId} confirmation failed: ${error.message}`, error);
|
|
71
|
+
error.code = 408; // Request Timeout
|
|
72
|
+
throw error;
|
|
34
73
|
}
|
|
35
|
-
const error = new Error(`Creation ID ${creationId} is taking too long.`);
|
|
36
|
-
error.code = 408; // Request Timeout
|
|
37
|
-
throw error;
|
|
38
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sends a GET request to the Threads API.
|
|
78
|
+
* @param {string} apiUrl - The API URL to send the request to.
|
|
79
|
+
* @param {Object} params - Query parameters for the request.
|
|
80
|
+
* @param {Object} logger - Logger instance for logging.
|
|
81
|
+
* @returns {Promise<Object>} The API response.
|
|
82
|
+
* @throws {Error} If the request fails.
|
|
83
|
+
*/
|
|
39
84
|
async function sendRequest(apiUrl, params, logger) {
|
|
40
85
|
try {
|
|
41
86
|
const response = await _superagent.default.get(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
@@ -50,6 +95,15 @@ async function sendRequest(apiUrl, params, logger) {
|
|
|
50
95
|
throw err;
|
|
51
96
|
}
|
|
52
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sends a POST request to the Threads API.
|
|
101
|
+
* @param {string} apiUrl - The API URL to send the request to.
|
|
102
|
+
* @param {Object} params - Query parameters for the request.
|
|
103
|
+
* @param {Object} logger - Logger instance for logging.
|
|
104
|
+
* @returns {Promise<Object>} The API response.
|
|
105
|
+
* @throws {Error} If the request fails.
|
|
106
|
+
*/
|
|
53
107
|
async function requestApi(apiUrl, params, logger) {
|
|
54
108
|
try {
|
|
55
109
|
const response = await _superagent.default.post(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
@@ -64,6 +118,15 @@ async function requestApi(apiUrl, params, logger) {
|
|
|
64
118
|
throw err;
|
|
65
119
|
}
|
|
66
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Publishes a post to Threads.
|
|
124
|
+
* @param {string} accessToken - The access token for authentication.
|
|
125
|
+
* @param {string} text - The text content of the post.
|
|
126
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
127
|
+
* @param {Object} logger - Logger instance for logging.
|
|
128
|
+
* @returns {Promise<Object>} The API response.
|
|
129
|
+
*/
|
|
67
130
|
async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
68
131
|
const query = await constructReplyQuery(accessToken, inReplyToId, text, asset, logger);
|
|
69
132
|
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
@@ -72,6 +135,33 @@ async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
|
72
135
|
});
|
|
73
136
|
return response.body;
|
|
74
137
|
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Publishes a quote post to Threads.
|
|
141
|
+
* @param {string} accessToken - The access token for authentication.
|
|
142
|
+
* @param {string} inReplyToId - The ID of the post to quote.
|
|
143
|
+
* @param {string} text - The text content of the quote.
|
|
144
|
+
* @param {Object} asset - The media asset to attach to the quote.
|
|
145
|
+
* @param {Object} logger - Logger instance for logging.
|
|
146
|
+
* @returns {Promise<Object>} The API response.
|
|
147
|
+
*/
|
|
148
|
+
async function quote(accessToken, inReplyToId, text, asset, logger) {
|
|
149
|
+
const query = await constructReplyQuery(accessToken, inReplyToId, text, asset, logger, true);
|
|
150
|
+
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
151
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API Publish quote Response`, {
|
|
152
|
+
responseBody: JSON.stringify(response.body)
|
|
153
|
+
});
|
|
154
|
+
return response.body;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Publishes a post to Threads.
|
|
159
|
+
* @param {string} accessToken - The access token for authentication.
|
|
160
|
+
* @param {string} text - The text content of the post.
|
|
161
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
162
|
+
* @param {Object} logger - Logger instance for logging.
|
|
163
|
+
* @returns {Promise<Object>} The API response.
|
|
164
|
+
*/
|
|
75
165
|
async function repost(token, externalId, logger) {
|
|
76
166
|
const postId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
77
167
|
let response = await requestApi(`${THREADS_URL}/${postId}/repost`, {
|
|
@@ -82,6 +172,15 @@ async function repost(token, externalId, logger) {
|
|
|
82
172
|
});
|
|
83
173
|
return response.body;
|
|
84
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Retrieves the profile information of a user.
|
|
178
|
+
* @param {string} token - The access token for authentication.
|
|
179
|
+
* @param {string} externalId - The external ID of the user.
|
|
180
|
+
* @param {string} fields - The fields to retrieve from the profile.
|
|
181
|
+
* @param {Object} logger - Logger instance for logging.
|
|
182
|
+
* @returns {Promise<Object>} The API response containing the profile information.
|
|
183
|
+
*/
|
|
85
184
|
async function getProfile(token, externalId, fields, logger) {
|
|
86
185
|
const userId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
87
186
|
let response = await sendRequest(`${THREADS_URL}/${userId}?fields=${fields}`, {
|
|
@@ -92,6 +191,15 @@ async function getProfile(token, externalId, fields, logger) {
|
|
|
92
191
|
});
|
|
93
192
|
return response.body;
|
|
94
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Retrieves the insights of a post.
|
|
197
|
+
* @param {string} token - The access token for authentication.
|
|
198
|
+
* @param {string} externalId - The external ID of the post.
|
|
199
|
+
* @param {string} metric - The metric to retrieve insights for.
|
|
200
|
+
* @param {Object} logger - Logger instance for logging.
|
|
201
|
+
* @returns {Promise<Object>} The API response containing the insights.
|
|
202
|
+
*/
|
|
95
203
|
async function getInsights(token, externalId, metric, logger) {
|
|
96
204
|
const mediaId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
97
205
|
let response = await sendRequest(`${THREADS_URL}/${mediaId}/insights`, {
|
|
@@ -103,11 +211,45 @@ async function getInsights(token, externalId, metric, logger) {
|
|
|
103
211
|
});
|
|
104
212
|
return response.body;
|
|
105
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Manages the visibility of a reply.
|
|
217
|
+
* @param {string} token - The access token for authentication.
|
|
218
|
+
* @param {string} externalId - The external ID of the reply.
|
|
219
|
+
* @param {boolean} hide - Whether to hide the reply.
|
|
220
|
+
* @param {Object} logger - Logger instance for logging.
|
|
221
|
+
* @returns {Promise<Object>} The API response.
|
|
222
|
+
*/
|
|
223
|
+
async function manageReply(token, externalId) {
|
|
224
|
+
let hide = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
225
|
+
let logger = arguments.length > 3 ? arguments[3] : undefined;
|
|
226
|
+
const replyId = (0, _externalIdHelpers.removePrefix)(externalId);
|
|
227
|
+
let response = await requestApi(`${THREADS_URL}/${replyId}/manage_reply`, {
|
|
228
|
+
access_token: token,
|
|
229
|
+
hide
|
|
230
|
+
}, logger);
|
|
231
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Native Threads API hideReply Response`, {
|
|
232
|
+
responseBody: JSON.stringify(response.body)
|
|
233
|
+
});
|
|
234
|
+
return response.body;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Constructs the query parameters for replying to a post.
|
|
239
|
+
* @param {string} accessToken - The access token for authentication.
|
|
240
|
+
* @param {string} inReplyToId - The ID of the post to reply to.
|
|
241
|
+
* @param {string} text - The text content of the reply.
|
|
242
|
+
* @param {Object} asset - The media asset to attach to the reply.
|
|
243
|
+
* @param {Object} logger - Logger instance for logging.
|
|
244
|
+
* @param {boolean} isQuote - Whether the reply is a quote.
|
|
245
|
+
* @returns {Promise<Object>} The constructed query parameters.
|
|
246
|
+
*/
|
|
106
247
|
async function constructReplyQuery(accessToken, inReplyToId, text, asset, logger) {
|
|
248
|
+
let isQuote = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
|
|
107
249
|
const baseQuery = {
|
|
108
250
|
access_token: accessToken,
|
|
109
|
-
|
|
110
|
-
|
|
251
|
+
text,
|
|
252
|
+
[isQuote ? 'quote_post_id' : 'reply_to_id']: inReplyToId
|
|
111
253
|
};
|
|
112
254
|
const mediaQuery = getMediaQuery(asset);
|
|
113
255
|
const query = {
|
|
@@ -119,13 +261,19 @@ async function constructReplyQuery(accessToken, inReplyToId, text, asset, logger
|
|
|
119
261
|
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
120
262
|
return {
|
|
121
263
|
...query,
|
|
122
|
-
containerResponse
|
|
264
|
+
creation_id: containerResponse.body.id
|
|
123
265
|
};
|
|
124
266
|
} catch (error) {
|
|
125
267
|
(0, _loggerHelpers.loggerError)(logger, 'Error constructing reply query', error);
|
|
126
268
|
throw error;
|
|
127
269
|
}
|
|
128
270
|
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Constructs the media query parameters based on the asset type.
|
|
274
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
275
|
+
* @returns {Object} The constructed media query parameters.
|
|
276
|
+
*/
|
|
129
277
|
function getMediaQuery(asset) {
|
|
130
278
|
if (!asset) {
|
|
131
279
|
return {
|
|
@@ -7,25 +7,68 @@ const MAX_RETRY_COUNT = 11;
|
|
|
7
7
|
const SHORT_WAIT_TIME_MS = 10000; // 10 seconds
|
|
8
8
|
const LONG_WAIT_TIME_MS = 60000; // 60 seconds
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Utility function to retry an asynchronous operation with exponential backoff.
|
|
12
|
+
* @param {Function} operation - The async operation to retry.
|
|
13
|
+
* @param {number} maxRetries - Maximum number of retries.
|
|
14
|
+
* @param {number} initialWaitTime - Initial wait time in milliseconds.
|
|
15
|
+
* @param {Function} logger - Logger instance for logging retries.
|
|
16
|
+
* @returns {Promise<any>} The result of the operation.
|
|
17
|
+
* @throws {Error} If the operation fails after all retries.
|
|
18
|
+
*/
|
|
19
|
+
async function retryWithBackoff(operation, maxRetries, initialWaitTime, logger) {
|
|
20
|
+
let waitTime = initialWaitTime;
|
|
21
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
22
|
+
try {
|
|
23
|
+
return await operation();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (attempt === maxRetries) {
|
|
26
|
+
throw error; // Rethrow error if max retries are reached
|
|
27
|
+
}
|
|
28
|
+
loggerInfo(logger, `Retry attempt ${attempt + 1} failed. Retrying in ${waitTime / 1000} seconds...`);
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
30
|
+
waitTime *= 2; // Exponential backoff
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Confirms the creation ID by polling the Threads API until the status is no longer "IN_PROGRESS".
|
|
37
|
+
* @param {string} accessToken - The access token for authentication.
|
|
38
|
+
* @param {string} creationId - The ID of the creation process to confirm.
|
|
39
|
+
* @param {Object} logger - Logger instance for logging.
|
|
40
|
+
* @returns {Promise<Object>} The API response when the status is no longer "IN_PROGRESS".
|
|
41
|
+
* @throws {Error} If the status remains "IN_PROGRESS" after all retries.
|
|
42
|
+
*/
|
|
10
43
|
async function confirmCreationId(accessToken, creationId, logger) {
|
|
11
|
-
|
|
44
|
+
const operation = async () => {
|
|
12
45
|
const response = await superagent.get(`${THREADS_URL}/${creationId}`).set('Accept', 'application/json').set('Content-Type', 'application/json').query({
|
|
13
46
|
access_token: accessToken,
|
|
14
47
|
fields: 'id,status,error_message'
|
|
15
48
|
}).send();
|
|
16
49
|
if (response.body.status === 'IN_PROGRESS') {
|
|
17
|
-
|
|
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;
|
|
50
|
+
throw new Error('Status is still IN_PROGRESS');
|
|
23
51
|
}
|
|
52
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
53
|
+
return response;
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
return await retryWithBackoff(operation, MAX_RETRY_COUNT, SHORT_WAIT_TIME_MS, logger);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
loggerError(logger, `Creation ID ${creationId} confirmation failed: ${error.message}`, error);
|
|
59
|
+
error.code = 408; // Request Timeout
|
|
60
|
+
throw error;
|
|
24
61
|
}
|
|
25
|
-
const error = new Error(`Creation ID ${creationId} is taking too long.`);
|
|
26
|
-
error.code = 408; // Request Timeout
|
|
27
|
-
throw error;
|
|
28
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sends a GET request to the Threads API.
|
|
66
|
+
* @param {string} apiUrl - The API URL to send the request to.
|
|
67
|
+
* @param {Object} params - Query parameters for the request.
|
|
68
|
+
* @param {Object} logger - Logger instance for logging.
|
|
69
|
+
* @returns {Promise<Object>} The API response.
|
|
70
|
+
* @throws {Error} If the request fails.
|
|
71
|
+
*/
|
|
29
72
|
async function sendRequest(apiUrl, params, logger) {
|
|
30
73
|
try {
|
|
31
74
|
const response = await superagent.get(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
@@ -40,6 +83,15 @@ async function sendRequest(apiUrl, params, logger) {
|
|
|
40
83
|
throw err;
|
|
41
84
|
}
|
|
42
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sends a POST request to the Threads API.
|
|
89
|
+
* @param {string} apiUrl - The API URL to send the request to.
|
|
90
|
+
* @param {Object} params - Query parameters for the request.
|
|
91
|
+
* @param {Object} logger - Logger instance for logging.
|
|
92
|
+
* @returns {Promise<Object>} The API response.
|
|
93
|
+
* @throws {Error} If the request fails.
|
|
94
|
+
*/
|
|
43
95
|
async function requestApi(apiUrl, params, logger) {
|
|
44
96
|
try {
|
|
45
97
|
const response = await superagent.post(apiUrl).set('Accept', 'application/json').set('Content-Type', 'application/json').query(params).send();
|
|
@@ -54,6 +106,15 @@ async function requestApi(apiUrl, params, logger) {
|
|
|
54
106
|
throw err;
|
|
55
107
|
}
|
|
56
108
|
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Publishes a post to Threads.
|
|
112
|
+
* @param {string} accessToken - The access token for authentication.
|
|
113
|
+
* @param {string} text - The text content of the post.
|
|
114
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
115
|
+
* @param {Object} logger - Logger instance for logging.
|
|
116
|
+
* @returns {Promise<Object>} The API response.
|
|
117
|
+
*/
|
|
57
118
|
export async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
58
119
|
const query = await constructReplyQuery(accessToken, inReplyToId, text, asset, logger);
|
|
59
120
|
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
@@ -62,6 +123,33 @@ export async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
|
62
123
|
});
|
|
63
124
|
return response.body;
|
|
64
125
|
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Publishes a quote post to Threads.
|
|
129
|
+
* @param {string} accessToken - The access token for authentication.
|
|
130
|
+
* @param {string} inReplyToId - The ID of the post to quote.
|
|
131
|
+
* @param {string} text - The text content of the quote.
|
|
132
|
+
* @param {Object} asset - The media asset to attach to the quote.
|
|
133
|
+
* @param {Object} logger - Logger instance for logging.
|
|
134
|
+
* @returns {Promise<Object>} The API response.
|
|
135
|
+
*/
|
|
136
|
+
export async function quote(accessToken, inReplyToId, text, asset, logger) {
|
|
137
|
+
const query = await constructReplyQuery(accessToken, inReplyToId, text, asset, logger, true);
|
|
138
|
+
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
139
|
+
loggerInfo(logger, `Native Threads API Publish quote Response`, {
|
|
140
|
+
responseBody: JSON.stringify(response.body)
|
|
141
|
+
});
|
|
142
|
+
return response.body;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Publishes a post to Threads.
|
|
147
|
+
* @param {string} accessToken - The access token for authentication.
|
|
148
|
+
* @param {string} text - The text content of the post.
|
|
149
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
150
|
+
* @param {Object} logger - Logger instance for logging.
|
|
151
|
+
* @returns {Promise<Object>} The API response.
|
|
152
|
+
*/
|
|
65
153
|
export async function repost(token, externalId, logger) {
|
|
66
154
|
const postId = removePrefix(externalId);
|
|
67
155
|
let response = await requestApi(`${THREADS_URL}/${postId}/repost`, {
|
|
@@ -72,6 +160,15 @@ export async function repost(token, externalId, logger) {
|
|
|
72
160
|
});
|
|
73
161
|
return response.body;
|
|
74
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Retrieves the profile information of a user.
|
|
166
|
+
* @param {string} token - The access token for authentication.
|
|
167
|
+
* @param {string} externalId - The external ID of the user.
|
|
168
|
+
* @param {string} fields - The fields to retrieve from the profile.
|
|
169
|
+
* @param {Object} logger - Logger instance for logging.
|
|
170
|
+
* @returns {Promise<Object>} The API response containing the profile information.
|
|
171
|
+
*/
|
|
75
172
|
export async function getProfile(token, externalId, fields, logger) {
|
|
76
173
|
const userId = removePrefix(externalId);
|
|
77
174
|
let response = await sendRequest(`${THREADS_URL}/${userId}?fields=${fields}`, {
|
|
@@ -82,6 +179,15 @@ export async function getProfile(token, externalId, fields, logger) {
|
|
|
82
179
|
});
|
|
83
180
|
return response.body;
|
|
84
181
|
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Retrieves the insights of a post.
|
|
185
|
+
* @param {string} token - The access token for authentication.
|
|
186
|
+
* @param {string} externalId - The external ID of the post.
|
|
187
|
+
* @param {string} metric - The metric to retrieve insights for.
|
|
188
|
+
* @param {Object} logger - Logger instance for logging.
|
|
189
|
+
* @returns {Promise<Object>} The API response containing the insights.
|
|
190
|
+
*/
|
|
85
191
|
export async function getInsights(token, externalId, metric, logger) {
|
|
86
192
|
const mediaId = removePrefix(externalId);
|
|
87
193
|
let response = await sendRequest(`${THREADS_URL}/${mediaId}/insights`, {
|
|
@@ -93,11 +199,45 @@ export async function getInsights(token, externalId, metric, logger) {
|
|
|
93
199
|
});
|
|
94
200
|
return response.body;
|
|
95
201
|
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Manages the visibility of a reply.
|
|
205
|
+
* @param {string} token - The access token for authentication.
|
|
206
|
+
* @param {string} externalId - The external ID of the reply.
|
|
207
|
+
* @param {boolean} hide - Whether to hide the reply.
|
|
208
|
+
* @param {Object} logger - Logger instance for logging.
|
|
209
|
+
* @returns {Promise<Object>} The API response.
|
|
210
|
+
*/
|
|
211
|
+
export async function manageReply(token, externalId) {
|
|
212
|
+
let hide = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
213
|
+
let logger = arguments.length > 3 ? arguments[3] : undefined;
|
|
214
|
+
const replyId = removePrefix(externalId);
|
|
215
|
+
let response = await requestApi(`${THREADS_URL}/${replyId}/manage_reply`, {
|
|
216
|
+
access_token: token,
|
|
217
|
+
hide
|
|
218
|
+
}, logger);
|
|
219
|
+
loggerInfo(logger, `Native Threads API hideReply Response`, {
|
|
220
|
+
responseBody: JSON.stringify(response.body)
|
|
221
|
+
});
|
|
222
|
+
return response.body;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Constructs the query parameters for replying to a post.
|
|
227
|
+
* @param {string} accessToken - The access token for authentication.
|
|
228
|
+
* @param {string} inReplyToId - The ID of the post to reply to.
|
|
229
|
+
* @param {string} text - The text content of the reply.
|
|
230
|
+
* @param {Object} asset - The media asset to attach to the reply.
|
|
231
|
+
* @param {Object} logger - Logger instance for logging.
|
|
232
|
+
* @param {boolean} isQuote - Whether the reply is a quote.
|
|
233
|
+
* @returns {Promise<Object>} The constructed query parameters.
|
|
234
|
+
*/
|
|
96
235
|
async function constructReplyQuery(accessToken, inReplyToId, text, asset, logger) {
|
|
236
|
+
let isQuote = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
|
|
97
237
|
const baseQuery = {
|
|
98
238
|
access_token: accessToken,
|
|
99
|
-
|
|
100
|
-
|
|
239
|
+
text,
|
|
240
|
+
[isQuote ? 'quote_post_id' : 'reply_to_id']: inReplyToId
|
|
101
241
|
};
|
|
102
242
|
const mediaQuery = getMediaQuery(asset);
|
|
103
243
|
const query = {
|
|
@@ -109,13 +249,19 @@ async function constructReplyQuery(accessToken, inReplyToId, text, asset, logger
|
|
|
109
249
|
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
110
250
|
return {
|
|
111
251
|
...query,
|
|
112
|
-
containerResponse
|
|
252
|
+
creation_id: containerResponse.body.id
|
|
113
253
|
};
|
|
114
254
|
} catch (error) {
|
|
115
255
|
loggerError(logger, 'Error constructing reply query', error);
|
|
116
256
|
throw error;
|
|
117
257
|
}
|
|
118
258
|
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Constructs the media query parameters based on the asset type.
|
|
262
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
263
|
+
* @returns {Object} The constructed media query parameters.
|
|
264
|
+
*/
|
|
119
265
|
function getMediaQuery(asset) {
|
|
120
266
|
if (!asset) {
|
|
121
267
|
return {
|
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.7",
|
|
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",
|
|
@@ -13,8 +13,53 @@ const MAX_RETRY_COUNT = 11;
|
|
|
13
13
|
const SHORT_WAIT_TIME_MS = 10000; // 10 seconds
|
|
14
14
|
const LONG_WAIT_TIME_MS = 60000; // 60 seconds
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Utility function to retry an asynchronous operation with exponential backoff.
|
|
18
|
+
* @param {Function} operation - The async operation to retry.
|
|
19
|
+
* @param {number} maxRetries - Maximum number of retries.
|
|
20
|
+
* @param {number} initialWaitTime - Initial wait time in milliseconds.
|
|
21
|
+
* @param {Function} logger - Logger instance for logging retries.
|
|
22
|
+
* @returns {Promise<any>} The result of the operation.
|
|
23
|
+
* @throws {Error} If the operation fails after all retries.
|
|
24
|
+
*/
|
|
25
|
+
async function retryWithBackoff(
|
|
26
|
+
operation,
|
|
27
|
+
maxRetries,
|
|
28
|
+
initialWaitTime,
|
|
29
|
+
logger
|
|
30
|
+
) {
|
|
31
|
+
let waitTime = initialWaitTime;
|
|
32
|
+
|
|
33
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
34
|
+
try {
|
|
35
|
+
return await operation();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (attempt === maxRetries) {
|
|
38
|
+
throw error; // Rethrow error if max retries are reached
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
loggerInfo(
|
|
42
|
+
logger,
|
|
43
|
+
`Retry attempt ${attempt + 1} failed. Retrying in ${
|
|
44
|
+
waitTime / 1000
|
|
45
|
+
} seconds...`
|
|
46
|
+
);
|
|
47
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
48
|
+
waitTime *= 2; // Exponential backoff
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Confirms the creation ID by polling the Threads API until the status is no longer "IN_PROGRESS".
|
|
55
|
+
* @param {string} accessToken - The access token for authentication.
|
|
56
|
+
* @param {string} creationId - The ID of the creation process to confirm.
|
|
57
|
+
* @param {Object} logger - Logger instance for logging.
|
|
58
|
+
* @returns {Promise<Object>} The API response when the status is no longer "IN_PROGRESS".
|
|
59
|
+
* @throws {Error} If the status remains "IN_PROGRESS" after all retries.
|
|
60
|
+
*/
|
|
16
61
|
async function confirmCreationId(accessToken, creationId, logger) {
|
|
17
|
-
|
|
62
|
+
const operation = async () => {
|
|
18
63
|
const response = await superagent
|
|
19
64
|
.get(`${THREADS_URL}/${creationId}`)
|
|
20
65
|
.set('Accept', 'application/json')
|
|
@@ -26,26 +71,39 @@ async function confirmCreationId(accessToken, creationId, logger) {
|
|
|
26
71
|
.send();
|
|
27
72
|
|
|
28
73
|
if (response.body.status === 'IN_PROGRESS') {
|
|
29
|
-
|
|
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;
|
|
74
|
+
throw new Error('Status is still IN_PROGRESS');
|
|
41
75
|
}
|
|
42
|
-
}
|
|
43
76
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
loggerDebug(logger, 'Threads Response status', response.status);
|
|
78
|
+
return response;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return await retryWithBackoff(
|
|
83
|
+
operation,
|
|
84
|
+
MAX_RETRY_COUNT,
|
|
85
|
+
SHORT_WAIT_TIME_MS,
|
|
86
|
+
logger
|
|
87
|
+
);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
loggerError(
|
|
90
|
+
logger,
|
|
91
|
+
`Creation ID ${creationId} confirmation failed: ${error.message}`,
|
|
92
|
+
error
|
|
93
|
+
);
|
|
94
|
+
error.code = 408; // Request Timeout
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
47
97
|
}
|
|
48
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Sends a GET request to the Threads API.
|
|
101
|
+
* @param {string} apiUrl - The API URL to send the request to.
|
|
102
|
+
* @param {Object} params - Query parameters for the request.
|
|
103
|
+
* @param {Object} logger - Logger instance for logging.
|
|
104
|
+
* @returns {Promise<Object>} The API response.
|
|
105
|
+
* @throws {Error} If the request fails.
|
|
106
|
+
*/
|
|
49
107
|
async function sendRequest(apiUrl, params, logger) {
|
|
50
108
|
try {
|
|
51
109
|
const response = await superagent
|
|
@@ -68,6 +126,14 @@ async function sendRequest(apiUrl, params, logger) {
|
|
|
68
126
|
}
|
|
69
127
|
}
|
|
70
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Sends a POST request to the Threads API.
|
|
131
|
+
* @param {string} apiUrl - The API URL to send the request to.
|
|
132
|
+
* @param {Object} params - Query parameters for the request.
|
|
133
|
+
* @param {Object} logger - Logger instance for logging.
|
|
134
|
+
* @returns {Promise<Object>} The API response.
|
|
135
|
+
* @throws {Error} If the request fails.
|
|
136
|
+
*/
|
|
71
137
|
async function requestApi(apiUrl, params, logger) {
|
|
72
138
|
try {
|
|
73
139
|
const response = await superagent
|
|
@@ -90,6 +156,14 @@ async function requestApi(apiUrl, params, logger) {
|
|
|
90
156
|
}
|
|
91
157
|
}
|
|
92
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Publishes a post to Threads.
|
|
161
|
+
* @param {string} accessToken - The access token for authentication.
|
|
162
|
+
* @param {string} text - The text content of the post.
|
|
163
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
164
|
+
* @param {Object} logger - Logger instance for logging.
|
|
165
|
+
* @returns {Promise<Object>} The API response.
|
|
166
|
+
*/
|
|
93
167
|
export async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
94
168
|
const query = await constructReplyQuery(
|
|
95
169
|
accessToken,
|
|
@@ -107,6 +181,39 @@ export async function reply(accessToken, inReplyToId, text, asset, logger) {
|
|
|
107
181
|
return response.body;
|
|
108
182
|
}
|
|
109
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Publishes a quote post to Threads.
|
|
186
|
+
* @param {string} accessToken - The access token for authentication.
|
|
187
|
+
* @param {string} inReplyToId - The ID of the post to quote.
|
|
188
|
+
* @param {string} text - The text content of the quote.
|
|
189
|
+
* @param {Object} asset - The media asset to attach to the quote.
|
|
190
|
+
* @param {Object} logger - Logger instance for logging.
|
|
191
|
+
* @returns {Promise<Object>} The API response.
|
|
192
|
+
*/
|
|
193
|
+
export async function quote(accessToken, inReplyToId, text, asset, logger) {
|
|
194
|
+
const query = await constructReplyQuery(
|
|
195
|
+
accessToken,
|
|
196
|
+
inReplyToId,
|
|
197
|
+
text,
|
|
198
|
+
asset,
|
|
199
|
+
logger,
|
|
200
|
+
true
|
|
201
|
+
);
|
|
202
|
+
let response = await requestApi(THREADS_PUBLISH_URL, query, logger);
|
|
203
|
+
loggerInfo(logger, `Native Threads API Publish quote Response`, {
|
|
204
|
+
responseBody: JSON.stringify(response.body),
|
|
205
|
+
});
|
|
206
|
+
return response.body;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Publishes a post to Threads.
|
|
211
|
+
* @param {string} accessToken - The access token for authentication.
|
|
212
|
+
* @param {string} text - The text content of the post.
|
|
213
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
214
|
+
* @param {Object} logger - Logger instance for logging.
|
|
215
|
+
* @returns {Promise<Object>} The API response.
|
|
216
|
+
*/
|
|
110
217
|
export async function repost(token, externalId, logger) {
|
|
111
218
|
const postId = removePrefix(externalId);
|
|
112
219
|
let response = await requestApi(
|
|
@@ -121,6 +228,14 @@ export async function repost(token, externalId, logger) {
|
|
|
121
228
|
return response.body;
|
|
122
229
|
}
|
|
123
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Retrieves the profile information of a user.
|
|
233
|
+
* @param {string} token - The access token for authentication.
|
|
234
|
+
* @param {string} externalId - The external ID of the user.
|
|
235
|
+
* @param {string} fields - The fields to retrieve from the profile.
|
|
236
|
+
* @param {Object} logger - Logger instance for logging.
|
|
237
|
+
* @returns {Promise<Object>} The API response containing the profile information.
|
|
238
|
+
*/
|
|
124
239
|
export async function getProfile(token, externalId, fields, logger) {
|
|
125
240
|
const userId = removePrefix(externalId);
|
|
126
241
|
let response = await sendRequest(
|
|
@@ -135,6 +250,14 @@ export async function getProfile(token, externalId, fields, logger) {
|
|
|
135
250
|
return response.body;
|
|
136
251
|
}
|
|
137
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Retrieves the insights of a post.
|
|
255
|
+
* @param {string} token - The access token for authentication.
|
|
256
|
+
* @param {string} externalId - The external ID of the post.
|
|
257
|
+
* @param {string} metric - The metric to retrieve insights for.
|
|
258
|
+
* @param {Object} logger - Logger instance for logging.
|
|
259
|
+
* @returns {Promise<Object>} The API response containing the insights.
|
|
260
|
+
*/
|
|
138
261
|
export async function getInsights(token, externalId, metric, logger) {
|
|
139
262
|
const mediaId = removePrefix(externalId);
|
|
140
263
|
let response = await sendRequest(
|
|
@@ -149,17 +272,50 @@ export async function getInsights(token, externalId, metric, logger) {
|
|
|
149
272
|
return response.body;
|
|
150
273
|
}
|
|
151
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Manages the visibility of a reply.
|
|
277
|
+
* @param {string} token - The access token for authentication.
|
|
278
|
+
* @param {string} externalId - The external ID of the reply.
|
|
279
|
+
* @param {boolean} hide - Whether to hide the reply.
|
|
280
|
+
* @param {Object} logger - Logger instance for logging.
|
|
281
|
+
* @returns {Promise<Object>} The API response.
|
|
282
|
+
*/
|
|
283
|
+
export async function manageReply(token, externalId, hide = false, logger) {
|
|
284
|
+
const replyId = removePrefix(externalId);
|
|
285
|
+
let response = await requestApi(
|
|
286
|
+
`${THREADS_URL}/${replyId}/manage_reply`,
|
|
287
|
+
{ access_token: token, hide },
|
|
288
|
+
logger
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
loggerInfo(logger, `Native Threads API hideReply Response`, {
|
|
292
|
+
responseBody: JSON.stringify(response.body),
|
|
293
|
+
});
|
|
294
|
+
return response.body;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Constructs the query parameters for replying to a post.
|
|
299
|
+
* @param {string} accessToken - The access token for authentication.
|
|
300
|
+
* @param {string} inReplyToId - The ID of the post to reply to.
|
|
301
|
+
* @param {string} text - The text content of the reply.
|
|
302
|
+
* @param {Object} asset - The media asset to attach to the reply.
|
|
303
|
+
* @param {Object} logger - Logger instance for logging.
|
|
304
|
+
* @param {boolean} isQuote - Whether the reply is a quote.
|
|
305
|
+
* @returns {Promise<Object>} The constructed query parameters.
|
|
306
|
+
*/
|
|
152
307
|
async function constructReplyQuery(
|
|
153
308
|
accessToken,
|
|
154
309
|
inReplyToId,
|
|
155
310
|
text,
|
|
156
311
|
asset,
|
|
157
|
-
logger
|
|
312
|
+
logger,
|
|
313
|
+
isQuote = false
|
|
158
314
|
) {
|
|
159
315
|
const baseQuery = {
|
|
160
316
|
access_token: accessToken,
|
|
161
|
-
reply_to_id: inReplyToId,
|
|
162
317
|
text,
|
|
318
|
+
[isQuote ? 'quote_post_id' : 'reply_to_id']: inReplyToId,
|
|
163
319
|
};
|
|
164
320
|
|
|
165
321
|
const mediaQuery = getMediaQuery(asset);
|
|
@@ -175,13 +331,18 @@ async function constructReplyQuery(
|
|
|
175
331
|
|
|
176
332
|
await confirmCreationId(accessToken, containerResponse.body.id, logger);
|
|
177
333
|
|
|
178
|
-
return { ...query, containerResponse };
|
|
334
|
+
return { ...query, creation_id: containerResponse.body.id };
|
|
179
335
|
} catch (error) {
|
|
180
336
|
loggerError(logger, 'Error constructing reply query', error);
|
|
181
337
|
throw error;
|
|
182
338
|
}
|
|
183
339
|
}
|
|
184
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Constructs the media query parameters based on the asset type.
|
|
343
|
+
* @param {Object} asset - The media asset to attach to the post.
|
|
344
|
+
* @returns {Object} The constructed media query parameters.
|
|
345
|
+
*/
|
|
185
346
|
function getMediaQuery(asset) {
|
|
186
347
|
if (!asset) {
|
|
187
348
|
return { media_type: 'TEXT' };
|