@meltwater/conversations-api-services 1.1.4 → 1.1.6

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