@meltwater/conversations-api-services 1.1.3 → 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.
@@ -3,90 +3,149 @@
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.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 = 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({
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
- 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;
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
- async function requestApi(apiUrl, accessToken, inReplyToId, text, asset, logger) {
34
- let response = {};
35
- try {
36
- let query = {
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
- if (!asset) {
42
- query = {
43
- ...query,
44
- media_type: 'TEXT'
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
- } else if (asset.type === 'video') {
54
- query = {
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
- 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;
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
  }
@@ -115,7 +115,8 @@ async function getMentionHandleInfo(token, externalId, logger) {
115
115
  token,
116
116
  uri,
117
117
  attachUrlPrefix: false,
118
- convertPayloadToUri: false
118
+ convertPayloadToUri: false,
119
+ logger
119
120
  });
120
121
  return result.data;
121
122
  } catch (error) {
@@ -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 = 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({
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
- 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;
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
- async function requestApi(apiUrl, accessToken, inReplyToId, text, asset, logger) {
27
- let response = {};
28
- try {
29
- let query = {
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
- if (!asset) {
35
- query = {
36
- ...query,
37
- media_type: 'TEXT'
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
- } else if (asset.type === 'video') {
47
- query = {
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
- 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;
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
  }
@@ -92,7 +92,8 @@ export async function getMentionHandleInfo(token, externalId, logger) {
92
92
  token,
93
93
  uri,
94
94
  attachUrlPrefix: false,
95
- convertPayloadToUri: false
95
+ convertPayloadToUri: false,
96
+ logger
96
97
  });
97
98
  return result.data;
98
99
  } catch (error) {
@@ -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",
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 { 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
+ 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
- loggerDebug(logger,'Threads Response status', response.status);
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
- return response;
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
- 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
- }
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(query)
55
+ .query(params)
87
56
  .send();
88
- await confirmCreationId(accessToken, containerResponse.body.id, logger);
89
- response = await superagent
90
- .post(THREADS_PUBLISH_URL)
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
- } 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
- );
80
+ if (response.status !== 200) {
81
+ throw new Error(`Unexpected response status: ${response.status}`);
114
82
  }
115
- throw err;
116
- }
117
83
 
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;
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
- export async function comment(accessToken, inReplyToId, text,asset, logger) {
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
- removePrefix(inReplyToId),
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
- `Native Threads API Publish Comment Response`,
148
- { responseBody: JSON.stringify(response.body) }
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
+ }
@@ -100,6 +100,7 @@ export async function getMentionHandleInfo(token, externalId, logger) {
100
100
  uri,
101
101
  attachUrlPrefix: false,
102
102
  convertPayloadToUri: false,
103
+ logger,
103
104
  });
104
105
  return result.data;
105
106
  } catch (error) {
@@ -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) {