@meltwater/conversations-api-services 1.1.21 → 1.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/data-access/http/facebook.native.js +13 -0
- package/dist/cjs/data-access/http/facebookApi.client.js +13 -0
- package/dist/cjs/data-access/http/twitter.native.js +153 -26
- package/dist/cjs/lib/externalId.helpers.js +4 -1
- package/dist/esm/data-access/http/facebook.native.js +13 -0
- package/dist/esm/data-access/http/facebookApi.client.js +13 -0
- package/dist/esm/data-access/http/twitter.native.js +152 -25
- package/dist/esm/lib/externalId.helpers.js +4 -1
- package/package.json +1 -1
- package/src/data-access/http/facebook.native.js +14 -0
- package/src/data-access/http/facebookApi.client.js +14 -0
- package/src/data-access/http/twitter.native.js +137 -12
- package/src/lib/externalId.helpers.js +4 -1
|
@@ -361,6 +361,19 @@ async function postApi(apiUrl, accessToken, payload, logger) {
|
|
|
361
361
|
}
|
|
362
362
|
response = await request;
|
|
363
363
|
} catch (err) {
|
|
364
|
+
// Handle specific case where content is already marked as spam
|
|
365
|
+
if (err?.response?.body?.error?.error_subcode === 1446036 && err?.response?.body?.error?.error_user_title === "Duplicate Mark Spam Request") {
|
|
366
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Content already marked as spam - treating as successful hide operation`, {
|
|
367
|
+
responseBody: JSON.stringify(err.response.body)
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
status: 200,
|
|
371
|
+
body: {
|
|
372
|
+
success: true,
|
|
373
|
+
message: "Content already hidden (marked as spam)"
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
364
377
|
(0, _loggerHelpers.loggerDebug)(logger, `Failed to call facebook api`, err);
|
|
365
378
|
throw err;
|
|
366
379
|
}
|
|
@@ -190,6 +190,19 @@ class FacebookApiClient {
|
|
|
190
190
|
access_token: accessToken
|
|
191
191
|
}).send(payload);
|
|
192
192
|
} catch (err) {
|
|
193
|
+
// Handle specific case where content is already marked as spam
|
|
194
|
+
if (err?.response?.body?.error?.error_subcode === 1446036 && err?.response?.body?.error?.error_user_title === "Duplicate Mark Spam Request") {
|
|
195
|
+
loggerChild.info(`Content already marked as spam - treating as successful hide operation for documentId ${documentId}`, {
|
|
196
|
+
responseBody: JSON.stringify(err.response.body)
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
status: 200,
|
|
200
|
+
body: {
|
|
201
|
+
success: true,
|
|
202
|
+
message: "Content already hidden (marked as spam)"
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
193
206
|
let errorText = '';
|
|
194
207
|
if (err && err.response && err.response.body && err.response.body.error) {
|
|
195
208
|
errorText = `Failed to call facebook api for documentId ${documentId}: ${err.response.body.error.message}`;
|
|
@@ -29,6 +29,112 @@ var _axios = _interopRequireDefault(require("axios"));
|
|
|
29
29
|
var _fs = _interopRequireDefault(require("fs"));
|
|
30
30
|
var _socialTwit = _interopRequireDefault(require("@meltwater/social-twit"));
|
|
31
31
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
32
|
+
const TWITTER_API_CONFIG = {
|
|
33
|
+
v1_1: {
|
|
34
|
+
base_url: 'https://api.twitter.com/1.1'
|
|
35
|
+
},
|
|
36
|
+
v2: {
|
|
37
|
+
base_url: 'https://api.x.com/2',
|
|
38
|
+
default_user_fields: 'id,name,username,profile_image_url,public_metrics,description,created_at,verified,protected'
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Helper function to create v2 API URLs with consistent field selection
|
|
43
|
+
function createTwitterV2Url(endpoint) {
|
|
44
|
+
let additionalParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
45
|
+
const baseUrl = `${TWITTER_API_CONFIG.v2.base_url}/${endpoint}`;
|
|
46
|
+
const defaultFields = {
|
|
47
|
+
'user.fields': TWITTER_API_CONFIG.v2.default_user_fields
|
|
48
|
+
};
|
|
49
|
+
const allParams = {
|
|
50
|
+
...defaultFields,
|
|
51
|
+
...additionalParams
|
|
52
|
+
};
|
|
53
|
+
const queryString = Object.entries(allParams).map(_ref => {
|
|
54
|
+
let [key, value] = _ref;
|
|
55
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
56
|
+
}).join('&');
|
|
57
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Helper function to make v2 API requests with fallback to v1.1
|
|
61
|
+
async function makeTwitterV2Request(v2Endpoint, v1FallbackQuery, token, logger) {
|
|
62
|
+
let requestOptions = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
|
|
63
|
+
// Try v2 API first - it supports OAuth 1.0a for user lookup endpoints and has better rate limits!
|
|
64
|
+
try {
|
|
65
|
+
const v2Url = createTwitterV2Url(v2Endpoint, requestOptions.v2Params);
|
|
66
|
+
const result = await getRequest({
|
|
67
|
+
token,
|
|
68
|
+
uri: v2Url,
|
|
69
|
+
attachUrlPrefix: false,
|
|
70
|
+
convertPayloadToUri: false,
|
|
71
|
+
logger
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
data: result.data?.data,
|
|
76
|
+
source: 'v2'
|
|
77
|
+
};
|
|
78
|
+
} catch (e) {
|
|
79
|
+
(0, _loggerHelpers.loggerWarn)(logger, `Twitter API v2 request failed for ${v2Endpoint}, falling back to v1.1`, e);
|
|
80
|
+
|
|
81
|
+
// Fallback to v1.1 if v2 fails
|
|
82
|
+
if (v1FallbackQuery && requestOptions.enableFallback !== false) {
|
|
83
|
+
try {
|
|
84
|
+
(0, _loggerHelpers.loggerInfo)(logger, `Falling back to v1.1 API for ${v2Endpoint}`);
|
|
85
|
+
const fallbackResult = await (requestOptions.fallbackMethod === 'post' ? postRequest : getRequest)({
|
|
86
|
+
token,
|
|
87
|
+
uri: v1FallbackQuery,
|
|
88
|
+
logger
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
data: fallbackResult.data,
|
|
93
|
+
source: 'v1.1'
|
|
94
|
+
};
|
|
95
|
+
} catch (fallbackError) {
|
|
96
|
+
(0, _loggerHelpers.loggerError)(logger, `Both v2 and v1.1 APIs failed for ${v2Endpoint}`, fallbackError);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: e,
|
|
102
|
+
source: 'failed'
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function normalizeUserData(user) {
|
|
107
|
+
let apiVersion = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'v1.1';
|
|
108
|
+
if (!user) return user;
|
|
109
|
+
const normalized = {
|
|
110
|
+
...user
|
|
111
|
+
};
|
|
112
|
+
if (apiVersion === 'v2') {
|
|
113
|
+
normalized.screen_name = user.username;
|
|
114
|
+
normalized.id_str = user.id;
|
|
115
|
+
normalized.profile_image_url_https = user.profile_image_url;
|
|
116
|
+
} else {
|
|
117
|
+
normalized.username = user.screen_name;
|
|
118
|
+
normalized.id = user.id_str;
|
|
119
|
+
normalized.profile_image_url = user.profile_image_url_https;
|
|
120
|
+
}
|
|
121
|
+
return normalized;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Normalizes an array of user data or a single user object
|
|
126
|
+
* @param {Object|Array} users - User object or array of user objects from Twitter API
|
|
127
|
+
* @param {string} apiVersion - API version used ('v1.1' or 'v2')
|
|
128
|
+
* @returns {Object|Array} Normalized user data with both screen_name and username
|
|
129
|
+
*/
|
|
130
|
+
function normalizeUsersData(users) {
|
|
131
|
+
let apiVersion = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'v1.1';
|
|
132
|
+
if (!users) return users;
|
|
133
|
+
if (Array.isArray(users)) {
|
|
134
|
+
return users.map(user => normalizeUserData(user, apiVersion));
|
|
135
|
+
}
|
|
136
|
+
return normalizeUserData(users, apiVersion);
|
|
137
|
+
}
|
|
32
138
|
function addOAuthToToken(token, TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET) {
|
|
33
139
|
if (!token.oauth) {
|
|
34
140
|
token.oauth = (0, _oauth.default)({
|
|
@@ -50,31 +156,49 @@ async function getUserInfoFromHandles(token, handles, logger) {
|
|
|
50
156
|
try {
|
|
51
157
|
const handlesJoin = handles.join(',');
|
|
52
158
|
if (!handlesJoin.length) {
|
|
53
|
-
loggerWarn(logger, `No handles provided to twitterNative getUserInfoFromHandles`);
|
|
159
|
+
(0, _loggerHelpers.loggerWarn)(logger, `No handles provided to twitterNative getUserInfoFromHandles`);
|
|
54
160
|
return [];
|
|
55
161
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
162
|
+
|
|
163
|
+
// Use Twitter API v2 for much better rate limits (900 req/15min vs v1.1 limits)
|
|
164
|
+
// v2 API supports up to 100 usernames per request vs v1.1's limit
|
|
165
|
+
const result = await makeTwitterV2Request('users/by',
|
|
166
|
+
// v2 endpoint
|
|
167
|
+
`users/lookup.json?screen_name=${handlesJoin}`,
|
|
168
|
+
// v1.1 fallback
|
|
169
|
+
token, logger, {
|
|
170
|
+
v2Params: {
|
|
171
|
+
usernames: handlesJoin
|
|
172
|
+
},
|
|
173
|
+
fallbackMethod: 'post'
|
|
61
174
|
});
|
|
62
|
-
|
|
175
|
+
const normalizedData = normalizeUsersData(result.data, result.source);
|
|
176
|
+
return normalizedData || [];
|
|
63
177
|
} catch (e) {
|
|
64
|
-
(0, _loggerHelpers.loggerError)(logger, `
|
|
178
|
+
(0, _loggerHelpers.loggerError)(logger, `Unexpected error in getUserInfoFromHandles`, e);
|
|
179
|
+
return [];
|
|
65
180
|
}
|
|
66
181
|
}
|
|
67
182
|
async function getUserInfoFromHandle(token, handleId, logger) {
|
|
68
183
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
184
|
+
if (!handleId) {
|
|
185
|
+
(0, _loggerHelpers.loggerWarn)(logger, `Invalid handleId provided to getUserInfoFromHandle: ${JSON.stringify(handleId)}`);
|
|
186
|
+
throw new Error(`Invalid handleId: ${handleId}`);
|
|
187
|
+
}
|
|
188
|
+
// Use Twitter API v2 for much better rate limits (900 req/15min vs v1.1 limits)
|
|
189
|
+
const result = await makeTwitterV2Request(`users/${(0, _externalIdHelpers.removePrefix)(handleId)}`,
|
|
190
|
+
// v2 endpoint
|
|
191
|
+
`users/show.json?user_id=${(0, _externalIdHelpers.removePrefix)(handleId)}`,
|
|
192
|
+
// v1.1 fallback
|
|
193
|
+
token, logger);
|
|
194
|
+
if (result.success) {
|
|
195
|
+
// Normalize the user data to ensure both screen_name and username are present
|
|
196
|
+
return normalizeUserData(result.data, result.source);
|
|
197
|
+
} else {
|
|
198
|
+
throw result.error || new Error('Failed to get user info from both APIs');
|
|
199
|
+
}
|
|
76
200
|
} catch (e) {
|
|
77
|
-
(0, _loggerHelpers.loggerError)(logger, `Error
|
|
201
|
+
(0, _loggerHelpers.loggerError)(logger, `Error in getUserInfoFromHandle with handleId: ${JSON.stringify(handleId)}`, e);
|
|
78
202
|
}
|
|
79
203
|
}
|
|
80
204
|
async function getDirectMessageImage(token, imageUrl, logger) {
|
|
@@ -308,7 +432,10 @@ async function reply(token, text, attachment, inReplyToId, removeInReplyToId, lo
|
|
|
308
432
|
}
|
|
309
433
|
};
|
|
310
434
|
if (removeInReplyToId.length) {
|
|
311
|
-
|
|
435
|
+
const filteredIds = removeInReplyToId.map(_externalIdHelpers.removePrefix).filter(id => id && id.trim().length > 0);
|
|
436
|
+
if (filteredIds.length > 0) {
|
|
437
|
+
payload.reply.exclude_reply_user_ids = filteredIds;
|
|
438
|
+
}
|
|
312
439
|
}
|
|
313
440
|
let query = 'https://api.twitter.com/2/tweets';
|
|
314
441
|
return publishTweet(token, payload, query, false, logger);
|
|
@@ -400,7 +527,7 @@ async function publishTweet(token, payload, query, isDirectMessage, logger) {
|
|
|
400
527
|
throw err;
|
|
401
528
|
}
|
|
402
529
|
}
|
|
403
|
-
async function postRequest(
|
|
530
|
+
async function postRequest(_ref2) {
|
|
404
531
|
let {
|
|
405
532
|
token,
|
|
406
533
|
uri,
|
|
@@ -408,7 +535,7 @@ async function postRequest(_ref) {
|
|
|
408
535
|
attachUrlPrefix = true,
|
|
409
536
|
convertPayloadToUri = true,
|
|
410
537
|
logger
|
|
411
|
-
} =
|
|
538
|
+
} = _ref2;
|
|
412
539
|
return doRequest({
|
|
413
540
|
requestMethod: 'post',
|
|
414
541
|
token,
|
|
@@ -419,7 +546,7 @@ async function postRequest(_ref) {
|
|
|
419
546
|
logger
|
|
420
547
|
});
|
|
421
548
|
}
|
|
422
|
-
async function getRequest(
|
|
549
|
+
async function getRequest(_ref3) {
|
|
423
550
|
let {
|
|
424
551
|
token,
|
|
425
552
|
uri,
|
|
@@ -427,7 +554,7 @@ async function getRequest(_ref2) {
|
|
|
427
554
|
attachUrlPrefix = true,
|
|
428
555
|
convertPayloadToUri = true,
|
|
429
556
|
logger
|
|
430
|
-
} =
|
|
557
|
+
} = _ref3;
|
|
431
558
|
return doRequest({
|
|
432
559
|
requestMethod: 'get',
|
|
433
560
|
payload,
|
|
@@ -438,7 +565,7 @@ async function getRequest(_ref2) {
|
|
|
438
565
|
logger
|
|
439
566
|
});
|
|
440
567
|
}
|
|
441
|
-
async function deleteRequest(
|
|
568
|
+
async function deleteRequest(_ref4) {
|
|
442
569
|
let {
|
|
443
570
|
token,
|
|
444
571
|
uri,
|
|
@@ -446,7 +573,7 @@ async function deleteRequest(_ref3) {
|
|
|
446
573
|
attachUrlPrefix = false,
|
|
447
574
|
convertPayloadToUri = false,
|
|
448
575
|
logger
|
|
449
|
-
} =
|
|
576
|
+
} = _ref4;
|
|
450
577
|
return doRequest({
|
|
451
578
|
requestMethod: 'delete',
|
|
452
579
|
token,
|
|
@@ -462,7 +589,7 @@ function fixedEncodeURIComponent(str) {
|
|
|
462
589
|
return '%' + c.charCodeAt(0).toString(16);
|
|
463
590
|
});
|
|
464
591
|
}
|
|
465
|
-
async function doRequest(
|
|
592
|
+
async function doRequest(_ref5) {
|
|
466
593
|
let {
|
|
467
594
|
requestMethod,
|
|
468
595
|
token,
|
|
@@ -471,7 +598,7 @@ async function doRequest(_ref4) {
|
|
|
471
598
|
attachUrlPrefix = true,
|
|
472
599
|
convertPayloadToUri = true,
|
|
473
600
|
logger
|
|
474
|
-
} =
|
|
601
|
+
} = _ref5;
|
|
475
602
|
if (payload && convertPayloadToUri) {
|
|
476
603
|
uri = uri + (uri.endsWith('?') ? '' : '?') + Object.keys(payload).map(key => {
|
|
477
604
|
return fixedEncodeURIComponent(key) + '=' + fixedEncodeURIComponent(payload[key]);
|
|
@@ -510,7 +637,7 @@ async function doRequest(_ref4) {
|
|
|
510
637
|
});
|
|
511
638
|
} catch (e) {
|
|
512
639
|
if (e.response.status == 503) {
|
|
513
|
-
loggerWarn(logger, `Twitter API 503 error - Service Unavailable`, {
|
|
640
|
+
(0, _loggerHelpers.loggerWarn)(logger, `Twitter API 503 error - Service Unavailable`, {
|
|
514
641
|
url,
|
|
515
642
|
convertPayloadToUri,
|
|
516
643
|
payload: JSON.stringify(payload)
|
|
@@ -6,7 +6,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.removePrefix = removePrefix;
|
|
7
7
|
exports.twitterPrefixCheck = twitterPrefixCheck;
|
|
8
8
|
function removePrefix(document) {
|
|
9
|
-
|
|
9
|
+
if (!document || typeof document !== 'string') {
|
|
10
|
+
return document; // Return as-is if not a valid string
|
|
11
|
+
}
|
|
12
|
+
return document.replace(/id\:[a-zA-Z]+\.(com|net):/g, '');
|
|
10
13
|
}
|
|
11
14
|
function twitterPrefixCheck(socialOriginType, value) {
|
|
12
15
|
let result = value;
|
|
@@ -342,6 +342,19 @@ async function postApi(apiUrl, accessToken, payload, logger) {
|
|
|
342
342
|
}
|
|
343
343
|
response = await request;
|
|
344
344
|
} catch (err) {
|
|
345
|
+
// Handle specific case where content is already marked as spam
|
|
346
|
+
if (err?.response?.body?.error?.error_subcode === 1446036 && err?.response?.body?.error?.error_user_title === "Duplicate Mark Spam Request") {
|
|
347
|
+
loggerInfo(logger, `Content already marked as spam - treating as successful hide operation`, {
|
|
348
|
+
responseBody: JSON.stringify(err.response.body)
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
status: 200,
|
|
352
|
+
body: {
|
|
353
|
+
success: true,
|
|
354
|
+
message: "Content already hidden (marked as spam)"
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
345
358
|
loggerDebug(logger, `Failed to call facebook api`, err);
|
|
346
359
|
throw err;
|
|
347
360
|
}
|
|
@@ -181,6 +181,19 @@ export class FacebookApiClient {
|
|
|
181
181
|
access_token: accessToken
|
|
182
182
|
}).send(payload);
|
|
183
183
|
} catch (err) {
|
|
184
|
+
// Handle specific case where content is already marked as spam
|
|
185
|
+
if (err?.response?.body?.error?.error_subcode === 1446036 && err?.response?.body?.error?.error_user_title === "Duplicate Mark Spam Request") {
|
|
186
|
+
loggerChild.info(`Content already marked as spam - treating as successful hide operation for documentId ${documentId}`, {
|
|
187
|
+
responseBody: JSON.stringify(err.response.body)
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
status: 200,
|
|
191
|
+
body: {
|
|
192
|
+
success: true,
|
|
193
|
+
message: "Content already hidden (marked as spam)"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
184
197
|
let errorText = '';
|
|
185
198
|
if (err && err.response && err.response.body && err.response.body.error) {
|
|
186
199
|
errorText = `Failed to call facebook api for documentId ${documentId}: ${err.response.body.error.message}`;
|
|
@@ -1,11 +1,117 @@
|
|
|
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';
|
|
3
|
+
import { loggerDebug, loggerError, loggerInfo, loggerWarn } from '../../lib/logger.helpers.js';
|
|
4
4
|
import OAuth from 'oauth-1.0a';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import axios from 'axios';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import Twit from '@meltwater/social-twit';
|
|
9
|
+
const TWITTER_API_CONFIG = {
|
|
10
|
+
v1_1: {
|
|
11
|
+
base_url: 'https://api.twitter.com/1.1'
|
|
12
|
+
},
|
|
13
|
+
v2: {
|
|
14
|
+
base_url: 'https://api.x.com/2',
|
|
15
|
+
default_user_fields: 'id,name,username,profile_image_url,public_metrics,description,created_at,verified,protected'
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Helper function to create v2 API URLs with consistent field selection
|
|
20
|
+
function createTwitterV2Url(endpoint) {
|
|
21
|
+
let additionalParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
22
|
+
const baseUrl = `${TWITTER_API_CONFIG.v2.base_url}/${endpoint}`;
|
|
23
|
+
const defaultFields = {
|
|
24
|
+
'user.fields': TWITTER_API_CONFIG.v2.default_user_fields
|
|
25
|
+
};
|
|
26
|
+
const allParams = {
|
|
27
|
+
...defaultFields,
|
|
28
|
+
...additionalParams
|
|
29
|
+
};
|
|
30
|
+
const queryString = Object.entries(allParams).map(_ref => {
|
|
31
|
+
let [key, value] = _ref;
|
|
32
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
33
|
+
}).join('&');
|
|
34
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper function to make v2 API requests with fallback to v1.1
|
|
38
|
+
async function makeTwitterV2Request(v2Endpoint, v1FallbackQuery, token, logger) {
|
|
39
|
+
let requestOptions = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
|
|
40
|
+
// Try v2 API first - it supports OAuth 1.0a for user lookup endpoints and has better rate limits!
|
|
41
|
+
try {
|
|
42
|
+
const v2Url = createTwitterV2Url(v2Endpoint, requestOptions.v2Params);
|
|
43
|
+
const result = await getRequest({
|
|
44
|
+
token,
|
|
45
|
+
uri: v2Url,
|
|
46
|
+
attachUrlPrefix: false,
|
|
47
|
+
convertPayloadToUri: false,
|
|
48
|
+
logger
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
data: result.data?.data,
|
|
53
|
+
source: 'v2'
|
|
54
|
+
};
|
|
55
|
+
} catch (e) {
|
|
56
|
+
loggerWarn(logger, `Twitter API v2 request failed for ${v2Endpoint}, falling back to v1.1`, e);
|
|
57
|
+
|
|
58
|
+
// Fallback to v1.1 if v2 fails
|
|
59
|
+
if (v1FallbackQuery && requestOptions.enableFallback !== false) {
|
|
60
|
+
try {
|
|
61
|
+
loggerInfo(logger, `Falling back to v1.1 API for ${v2Endpoint}`);
|
|
62
|
+
const fallbackResult = await (requestOptions.fallbackMethod === 'post' ? postRequest : getRequest)({
|
|
63
|
+
token,
|
|
64
|
+
uri: v1FallbackQuery,
|
|
65
|
+
logger
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
data: fallbackResult.data,
|
|
70
|
+
source: 'v1.1'
|
|
71
|
+
};
|
|
72
|
+
} catch (fallbackError) {
|
|
73
|
+
loggerError(logger, `Both v2 and v1.1 APIs failed for ${v2Endpoint}`, fallbackError);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
error: e,
|
|
79
|
+
source: 'failed'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function normalizeUserData(user) {
|
|
84
|
+
let apiVersion = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'v1.1';
|
|
85
|
+
if (!user) return user;
|
|
86
|
+
const normalized = {
|
|
87
|
+
...user
|
|
88
|
+
};
|
|
89
|
+
if (apiVersion === 'v2') {
|
|
90
|
+
normalized.screen_name = user.username;
|
|
91
|
+
normalized.id_str = user.id;
|
|
92
|
+
normalized.profile_image_url_https = user.profile_image_url;
|
|
93
|
+
} else {
|
|
94
|
+
normalized.username = user.screen_name;
|
|
95
|
+
normalized.id = user.id_str;
|
|
96
|
+
normalized.profile_image_url = user.profile_image_url_https;
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Normalizes an array of user data or a single user object
|
|
103
|
+
* @param {Object|Array} users - User object or array of user objects from Twitter API
|
|
104
|
+
* @param {string} apiVersion - API version used ('v1.1' or 'v2')
|
|
105
|
+
* @returns {Object|Array} Normalized user data with both screen_name and username
|
|
106
|
+
*/
|
|
107
|
+
function normalizeUsersData(users) {
|
|
108
|
+
let apiVersion = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'v1.1';
|
|
109
|
+
if (!users) return users;
|
|
110
|
+
if (Array.isArray(users)) {
|
|
111
|
+
return users.map(user => normalizeUserData(user, apiVersion));
|
|
112
|
+
}
|
|
113
|
+
return normalizeUserData(users, apiVersion);
|
|
114
|
+
}
|
|
9
115
|
export function addOAuthToToken(token, TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET) {
|
|
10
116
|
if (!token.oauth) {
|
|
11
117
|
token.oauth = OAuth({
|
|
@@ -30,28 +136,46 @@ export async function getUserInfoFromHandles(token, handles, logger) {
|
|
|
30
136
|
loggerWarn(logger, `No handles provided to twitterNative getUserInfoFromHandles`);
|
|
31
137
|
return [];
|
|
32
138
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
139
|
+
|
|
140
|
+
// Use Twitter API v2 for much better rate limits (900 req/15min vs v1.1 limits)
|
|
141
|
+
// v2 API supports up to 100 usernames per request vs v1.1's limit
|
|
142
|
+
const result = await makeTwitterV2Request('users/by',
|
|
143
|
+
// v2 endpoint
|
|
144
|
+
`users/lookup.json?screen_name=${handlesJoin}`,
|
|
145
|
+
// v1.1 fallback
|
|
146
|
+
token, logger, {
|
|
147
|
+
v2Params: {
|
|
148
|
+
usernames: handlesJoin
|
|
149
|
+
},
|
|
150
|
+
fallbackMethod: 'post'
|
|
38
151
|
});
|
|
39
|
-
|
|
152
|
+
const normalizedData = normalizeUsersData(result.data, result.source);
|
|
153
|
+
return normalizedData || [];
|
|
40
154
|
} catch (e) {
|
|
41
|
-
loggerError(logger, `
|
|
155
|
+
loggerError(logger, `Unexpected error in getUserInfoFromHandles`, e);
|
|
156
|
+
return [];
|
|
42
157
|
}
|
|
43
158
|
}
|
|
44
159
|
export async function getUserInfoFromHandle(token, handleId, logger) {
|
|
45
160
|
try {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
161
|
+
if (!handleId) {
|
|
162
|
+
loggerWarn(logger, `Invalid handleId provided to getUserInfoFromHandle: ${JSON.stringify(handleId)}`);
|
|
163
|
+
throw new Error(`Invalid handleId: ${handleId}`);
|
|
164
|
+
}
|
|
165
|
+
// Use Twitter API v2 for much better rate limits (900 req/15min vs v1.1 limits)
|
|
166
|
+
const result = await makeTwitterV2Request(`users/${removePrefix(handleId)}`,
|
|
167
|
+
// v2 endpoint
|
|
168
|
+
`users/show.json?user_id=${removePrefix(handleId)}`,
|
|
169
|
+
// v1.1 fallback
|
|
170
|
+
token, logger);
|
|
171
|
+
if (result.success) {
|
|
172
|
+
// Normalize the user data to ensure both screen_name and username are present
|
|
173
|
+
return normalizeUserData(result.data, result.source);
|
|
174
|
+
} else {
|
|
175
|
+
throw result.error || new Error('Failed to get user info from both APIs');
|
|
176
|
+
}
|
|
53
177
|
} catch (e) {
|
|
54
|
-
loggerError(logger, `Error
|
|
178
|
+
loggerError(logger, `Error in getUserInfoFromHandle with handleId: ${JSON.stringify(handleId)}`, e);
|
|
55
179
|
}
|
|
56
180
|
}
|
|
57
181
|
export async function getDirectMessageImage(token, imageUrl, logger) {
|
|
@@ -285,7 +409,10 @@ export async function reply(token, text, attachment, inReplyToId, removeInReplyT
|
|
|
285
409
|
}
|
|
286
410
|
};
|
|
287
411
|
if (removeInReplyToId.length) {
|
|
288
|
-
|
|
412
|
+
const filteredIds = removeInReplyToId.map(removePrefix).filter(id => id && id.trim().length > 0);
|
|
413
|
+
if (filteredIds.length > 0) {
|
|
414
|
+
payload.reply.exclude_reply_user_ids = filteredIds;
|
|
415
|
+
}
|
|
289
416
|
}
|
|
290
417
|
let query = 'https://api.twitter.com/2/tweets';
|
|
291
418
|
return publishTweet(token, payload, query, false, logger);
|
|
@@ -377,7 +504,7 @@ async function publishTweet(token, payload, query, isDirectMessage, logger) {
|
|
|
377
504
|
throw err;
|
|
378
505
|
}
|
|
379
506
|
}
|
|
380
|
-
async function postRequest(
|
|
507
|
+
async function postRequest(_ref2) {
|
|
381
508
|
let {
|
|
382
509
|
token,
|
|
383
510
|
uri,
|
|
@@ -385,7 +512,7 @@ async function postRequest(_ref) {
|
|
|
385
512
|
attachUrlPrefix = true,
|
|
386
513
|
convertPayloadToUri = true,
|
|
387
514
|
logger
|
|
388
|
-
} =
|
|
515
|
+
} = _ref2;
|
|
389
516
|
return doRequest({
|
|
390
517
|
requestMethod: 'post',
|
|
391
518
|
token,
|
|
@@ -396,7 +523,7 @@ async function postRequest(_ref) {
|
|
|
396
523
|
logger
|
|
397
524
|
});
|
|
398
525
|
}
|
|
399
|
-
async function getRequest(
|
|
526
|
+
async function getRequest(_ref3) {
|
|
400
527
|
let {
|
|
401
528
|
token,
|
|
402
529
|
uri,
|
|
@@ -404,7 +531,7 @@ async function getRequest(_ref2) {
|
|
|
404
531
|
attachUrlPrefix = true,
|
|
405
532
|
convertPayloadToUri = true,
|
|
406
533
|
logger
|
|
407
|
-
} =
|
|
534
|
+
} = _ref3;
|
|
408
535
|
return doRequest({
|
|
409
536
|
requestMethod: 'get',
|
|
410
537
|
payload,
|
|
@@ -415,7 +542,7 @@ async function getRequest(_ref2) {
|
|
|
415
542
|
logger
|
|
416
543
|
});
|
|
417
544
|
}
|
|
418
|
-
async function deleteRequest(
|
|
545
|
+
async function deleteRequest(_ref4) {
|
|
419
546
|
let {
|
|
420
547
|
token,
|
|
421
548
|
uri,
|
|
@@ -423,7 +550,7 @@ async function deleteRequest(_ref3) {
|
|
|
423
550
|
attachUrlPrefix = false,
|
|
424
551
|
convertPayloadToUri = false,
|
|
425
552
|
logger
|
|
426
|
-
} =
|
|
553
|
+
} = _ref4;
|
|
427
554
|
return doRequest({
|
|
428
555
|
requestMethod: 'delete',
|
|
429
556
|
token,
|
|
@@ -439,7 +566,7 @@ function fixedEncodeURIComponent(str) {
|
|
|
439
566
|
return '%' + c.charCodeAt(0).toString(16);
|
|
440
567
|
});
|
|
441
568
|
}
|
|
442
|
-
async function doRequest(
|
|
569
|
+
async function doRequest(_ref5) {
|
|
443
570
|
let {
|
|
444
571
|
requestMethod,
|
|
445
572
|
token,
|
|
@@ -448,7 +575,7 @@ async function doRequest(_ref4) {
|
|
|
448
575
|
attachUrlPrefix = true,
|
|
449
576
|
convertPayloadToUri = true,
|
|
450
577
|
logger
|
|
451
|
-
} =
|
|
578
|
+
} = _ref5;
|
|
452
579
|
if (payload && convertPayloadToUri) {
|
|
453
580
|
uri = uri + (uri.endsWith('?') ? '' : '?') + Object.keys(payload).map(key => {
|
|
454
581
|
return fixedEncodeURIComponent(key) + '=' + fixedEncodeURIComponent(payload[key]);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export function removePrefix(document) {
|
|
2
|
-
|
|
2
|
+
if (!document || typeof document !== 'string') {
|
|
3
|
+
return document; // Return as-is if not a valid string
|
|
4
|
+
}
|
|
5
|
+
return document.replace(/id\:[a-zA-Z]+\.(com|net):/g, '');
|
|
3
6
|
}
|
|
4
7
|
export function twitterPrefixCheck(socialOriginType, value) {
|
|
5
8
|
let result = value;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meltwater/conversations-api-services",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.23",
|
|
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",
|
|
@@ -545,6 +545,20 @@ async function postApi(
|
|
|
545
545
|
|
|
546
546
|
response = await request;
|
|
547
547
|
} catch (err) {
|
|
548
|
+
// Handle specific case where content is already marked as spam
|
|
549
|
+
if (
|
|
550
|
+
err?.response?.body?.error?.error_subcode === 1446036 &&
|
|
551
|
+
err?.response?.body?.error?.error_user_title === "Duplicate Mark Spam Request"
|
|
552
|
+
) {
|
|
553
|
+
loggerInfo(logger, `Content already marked as spam - treating as successful hide operation`, {
|
|
554
|
+
responseBody: JSON.stringify(err.response.body),
|
|
555
|
+
});
|
|
556
|
+
return {
|
|
557
|
+
status: 200,
|
|
558
|
+
body: { success: true, message: "Content already hidden (marked as spam)" }
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
548
562
|
loggerDebug(logger, `Failed to call facebook api`, err);
|
|
549
563
|
throw err;
|
|
550
564
|
}
|
|
@@ -232,6 +232,20 @@ export class FacebookApiClient {
|
|
|
232
232
|
.query({ access_token: accessToken })
|
|
233
233
|
.send(payload);
|
|
234
234
|
} catch (err) {
|
|
235
|
+
// Handle specific case where content is already marked as spam
|
|
236
|
+
if (
|
|
237
|
+
err?.response?.body?.error?.error_subcode === 1446036 &&
|
|
238
|
+
err?.response?.body?.error?.error_user_title === "Duplicate Mark Spam Request"
|
|
239
|
+
) {
|
|
240
|
+
loggerChild.info(`Content already marked as spam - treating as successful hide operation for documentId ${documentId}`, {
|
|
241
|
+
responseBody: JSON.stringify(err.response.body),
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
status: 200,
|
|
245
|
+
body: { success: true, message: "Content already hidden (marked as spam)" }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
235
249
|
let errorText = '';
|
|
236
250
|
if (
|
|
237
251
|
err &&
|
|
@@ -1,12 +1,105 @@
|
|
|
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';
|
|
3
|
+
import { loggerDebug, loggerError, loggerInfo, loggerWarn } from '../../lib/logger.helpers.js';
|
|
4
4
|
import OAuth from 'oauth-1.0a';
|
|
5
5
|
import crypto from 'crypto';
|
|
6
6
|
import axios from 'axios';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import Twit from '@meltwater/social-twit';
|
|
9
9
|
|
|
10
|
+
const TWITTER_API_CONFIG = {
|
|
11
|
+
v1_1: {
|
|
12
|
+
base_url: 'https://api.twitter.com/1.1',
|
|
13
|
+
},
|
|
14
|
+
v2: {
|
|
15
|
+
base_url: 'https://api.x.com/2',
|
|
16
|
+
default_user_fields: 'id,name,username,profile_image_url,public_metrics,description,created_at,verified,protected'
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Helper function to create v2 API URLs with consistent field selection
|
|
21
|
+
function createTwitterV2Url(endpoint, additionalParams = {}) {
|
|
22
|
+
const baseUrl = `${TWITTER_API_CONFIG.v2.base_url}/${endpoint}`;
|
|
23
|
+
const defaultFields = { 'user.fields': TWITTER_API_CONFIG.v2.default_user_fields };
|
|
24
|
+
const allParams = { ...defaultFields, ...additionalParams };
|
|
25
|
+
|
|
26
|
+
const queryString = Object.entries(allParams)
|
|
27
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
28
|
+
.join('&');
|
|
29
|
+
|
|
30
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper function to make v2 API requests with fallback to v1.1
|
|
34
|
+
async function makeTwitterV2Request(v2Endpoint, v1FallbackQuery, token, logger, requestOptions = {}) {
|
|
35
|
+
// Try v2 API first - it supports OAuth 1.0a for user lookup endpoints and has better rate limits!
|
|
36
|
+
try {
|
|
37
|
+
const v2Url = createTwitterV2Url(v2Endpoint, requestOptions.v2Params);
|
|
38
|
+
|
|
39
|
+
const result = await getRequest({
|
|
40
|
+
token,
|
|
41
|
+
uri: v2Url,
|
|
42
|
+
attachUrlPrefix: false,
|
|
43
|
+
convertPayloadToUri: false,
|
|
44
|
+
logger
|
|
45
|
+
});
|
|
46
|
+
return { success: true, data: result.data?.data, source: 'v2' };
|
|
47
|
+
} catch (e) {
|
|
48
|
+
loggerWarn(logger, `Twitter API v2 request failed for ${v2Endpoint}, falling back to v1.1`, e);
|
|
49
|
+
|
|
50
|
+
// Fallback to v1.1 if v2 fails
|
|
51
|
+
if (v1FallbackQuery && requestOptions.enableFallback !== false) {
|
|
52
|
+
try {
|
|
53
|
+
loggerInfo(logger, `Falling back to v1.1 API for ${v2Endpoint}`);
|
|
54
|
+
const fallbackResult = await (requestOptions.fallbackMethod === 'post' ? postRequest : getRequest)({
|
|
55
|
+
token,
|
|
56
|
+
uri: v1FallbackQuery,
|
|
57
|
+
logger
|
|
58
|
+
});
|
|
59
|
+
return { success: true, data: fallbackResult.data, source: 'v1.1' };
|
|
60
|
+
} catch (fallbackError) {
|
|
61
|
+
loggerError(logger, `Both v2 and v1.1 APIs failed for ${v2Endpoint}`, fallbackError);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { success: false, error: e, source: 'failed' };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeUserData(user, apiVersion = 'v1.1') {
|
|
70
|
+
if (!user) return user;
|
|
71
|
+
|
|
72
|
+
const normalized = { ...user };
|
|
73
|
+
|
|
74
|
+
if (apiVersion === 'v2') {
|
|
75
|
+
normalized.screen_name = user.username;
|
|
76
|
+
normalized.id_str = user.id;
|
|
77
|
+
normalized.profile_image_url_https = user.profile_image_url;
|
|
78
|
+
} else {
|
|
79
|
+
normalized.username = user.screen_name;
|
|
80
|
+
normalized.id = user.id_str;
|
|
81
|
+
normalized.profile_image_url = user.profile_image_url_https;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return normalized;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes an array of user data or a single user object
|
|
89
|
+
* @param {Object|Array} users - User object or array of user objects from Twitter API
|
|
90
|
+
* @param {string} apiVersion - API version used ('v1.1' or 'v2')
|
|
91
|
+
* @returns {Object|Array} Normalized user data with both screen_name and username
|
|
92
|
+
*/
|
|
93
|
+
function normalizeUsersData(users, apiVersion = 'v1.1') {
|
|
94
|
+
if (!users) return users;
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(users)) {
|
|
97
|
+
return users.map(user => normalizeUserData(user, apiVersion));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return normalizeUserData(users, apiVersion);
|
|
101
|
+
}
|
|
102
|
+
|
|
10
103
|
|
|
11
104
|
|
|
12
105
|
export function addOAuthToToken(token, TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET) {
|
|
@@ -37,21 +130,50 @@ export async function getUserInfoFromHandles(token, handles, logger) {
|
|
|
37
130
|
loggerWarn(logger,`No handles provided to twitterNative getUserInfoFromHandles`);
|
|
38
131
|
return [];
|
|
39
132
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
133
|
+
|
|
134
|
+
// Use Twitter API v2 for much better rate limits (900 req/15min vs v1.1 limits)
|
|
135
|
+
// v2 API supports up to 100 usernames per request vs v1.1's limit
|
|
136
|
+
const result = await makeTwitterV2Request(
|
|
137
|
+
'users/by', // v2 endpoint
|
|
138
|
+
`users/lookup.json?screen_name=${handlesJoin}`, // v1.1 fallback
|
|
139
|
+
token,
|
|
140
|
+
logger,
|
|
141
|
+
{
|
|
142
|
+
v2Params: { usernames: handlesJoin },
|
|
143
|
+
fallbackMethod: 'post'
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const normalizedData = normalizeUsersData(result.data, result.source);
|
|
148
|
+
return normalizedData || [];
|
|
43
149
|
} catch (e) {
|
|
44
|
-
loggerError(logger
|
|
150
|
+
loggerError(logger, `Unexpected error in getUserInfoFromHandles`, e);
|
|
151
|
+
return [];
|
|
45
152
|
}
|
|
46
153
|
}
|
|
47
154
|
|
|
48
155
|
export async function getUserInfoFromHandle(token, handleId, logger) {
|
|
49
156
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
157
|
+
if (!handleId) {
|
|
158
|
+
loggerWarn(logger, `Invalid handleId provided to getUserInfoFromHandle: ${JSON.stringify(handleId)}`);
|
|
159
|
+
throw new Error(`Invalid handleId: ${handleId}`);
|
|
160
|
+
}
|
|
161
|
+
// Use Twitter API v2 for much better rate limits (900 req/15min vs v1.1 limits)
|
|
162
|
+
const result = await makeTwitterV2Request(
|
|
163
|
+
`users/${removePrefix(handleId)}`, // v2 endpoint
|
|
164
|
+
`users/show.json?user_id=${removePrefix(handleId)}`, // v1.1 fallback
|
|
165
|
+
token,
|
|
166
|
+
logger
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (result.success) {
|
|
170
|
+
// Normalize the user data to ensure both screen_name and username are present
|
|
171
|
+
return normalizeUserData(result.data, result.source);
|
|
172
|
+
} else {
|
|
173
|
+
throw result.error || new Error('Failed to get user info from both APIs');
|
|
174
|
+
}
|
|
53
175
|
} catch (e) {
|
|
54
|
-
loggerError(logger
|
|
176
|
+
loggerError(logger, `Error in getUserInfoFromHandle with handleId: ${JSON.stringify(handleId)}`, e);
|
|
55
177
|
}
|
|
56
178
|
}
|
|
57
179
|
|
|
@@ -335,9 +457,12 @@ export async function reply(token, text, attachment, inReplyToId, removeInReplyT
|
|
|
335
457
|
},
|
|
336
458
|
};
|
|
337
459
|
if (removeInReplyToId.length){
|
|
338
|
-
|
|
339
|
-
removePrefix
|
|
340
|
-
|
|
460
|
+
const filteredIds = removeInReplyToId
|
|
461
|
+
.map(removePrefix)
|
|
462
|
+
.filter(id => id && id.trim().length > 0);
|
|
463
|
+
if (filteredIds.length > 0) {
|
|
464
|
+
payload.reply.exclude_reply_user_ids = filteredIds;
|
|
465
|
+
}
|
|
341
466
|
}
|
|
342
467
|
|
|
343
468
|
let query = 'https://api.twitter.com/2/tweets';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export function removePrefix(document) {
|
|
2
|
-
|
|
2
|
+
if (!document || typeof document !== 'string') {
|
|
3
|
+
return document; // Return as-is if not a valid string
|
|
4
|
+
}
|
|
5
|
+
return document.replace(/id\:[a-zA-Z]+\.(com|net):/g, '');
|
|
3
6
|
}
|
|
4
7
|
|
|
5
8
|
export function twitterPrefixCheck(socialOriginType, value) {
|