@meltwater/conversations-api-services 1.1.24 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.addOAuthToToken = addOAuthToToken;
7
7
  exports.followUser = followUser;
8
+ exports.getAuthenticatedUser = getAuthenticatedUser;
8
9
  exports.getBrandUserRelationship = getBrandUserRelationship;
9
10
  exports.getCurrentInfo = getCurrentInfo;
10
11
  exports.getDirectMessageImage = getDirectMessageImage;
@@ -27,7 +28,6 @@ var _oauth = _interopRequireDefault(require("oauth-1.0a"));
27
28
  var _crypto = _interopRequireDefault(require("crypto"));
28
29
  var _axios = _interopRequireDefault(require("axios"));
29
30
  var _fs = _interopRequireDefault(require("fs"));
30
- var _socialTwit = _interopRequireDefault(require("@meltwater/social-twit"));
31
31
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
32
32
  const TWITTER_API_CONFIG = {
33
33
  v1_1: {
@@ -135,6 +135,28 @@ function normalizeUsersData(users) {
135
135
  }
136
136
  return normalizeUserData(users, apiVersion);
137
137
  }
138
+
139
+ /**
140
+ * Get authenticated user information using Twitter API v2
141
+ * @param {Object} token - OAuth token object
142
+ * @param {Object} logger - Logger instance
143
+ * @returns {Promise<Object>} User data from v2 API
144
+ */
145
+ async function getAuthenticatedUser(token, logger) {
146
+ try {
147
+ const result = await getRequest({
148
+ token,
149
+ uri: 'https://api.x.com/2/users/me',
150
+ attachUrlPrefix: false,
151
+ convertPayloadToUri: false,
152
+ logger
153
+ });
154
+ return result.data;
155
+ } catch (e) {
156
+ (0, _loggerHelpers.loggerError)(logger, `Error getting authenticated user info`, e);
157
+ throw e;
158
+ }
159
+ }
138
160
  function addOAuthToToken(token, TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET) {
139
161
  if (!token.oauth) {
140
162
  token.oauth = (0, _oauth.default)({
@@ -230,7 +252,7 @@ async function getCurrentInfo(token, externalId, logger) {
230
252
  });
231
253
 
232
254
  // this makes it so 'retweeted' is only true on a retweet that isn't a comment
233
- // sometimes 'retweeted' is true if user retweeted with comment, sometimes not :sadness:
255
+ // sometimes 'retweeted' is true if user retweeted with comment, sometimes not
234
256
  result.data.retweeted = !!result.data.current_user_retweet;
235
257
  return result.data.data[0].public_metrics;
236
258
  } catch (error) {
@@ -293,12 +315,20 @@ async function unRetweet(token, sourceId, externalId, logger) {
293
315
  }
294
316
  async function like(token, externalId, logger) {
295
317
  try {
318
+ // Get the authenticated user's ID first
319
+ const userInfo = await getAuthenticatedUser(token, logger);
320
+ const userId = userInfo.data.id;
296
321
  let response = await postRequest({
297
322
  token,
298
- uri: 'favorites/create.json?id=' + (0, _externalIdHelpers.removePrefix)(externalId),
323
+ uri: `https://api.x.com/2/users/${userId}/likes`,
324
+ payload: {
325
+ tweet_id: (0, _externalIdHelpers.removePrefix)(externalId)
326
+ },
327
+ attachUrlPrefix: false,
328
+ convertPayloadToUri: false,
299
329
  logger
300
330
  });
301
- if (response.data.favorited !== undefined && response.resp.statusCode === 200) {
331
+ if (response.data.data?.liked !== undefined && response.resp.statusCode === 200) {
302
332
  return true;
303
333
  } else {
304
334
  (0, _loggerHelpers.loggerInfo)(logger, 'Twitter Error in like user statusCode non 200 ', {
@@ -313,12 +343,17 @@ async function like(token, externalId, logger) {
313
343
  }
314
344
  async function unLike(token, externalId, logger) {
315
345
  try {
316
- let response = await postRequest({
346
+ // Get the authenticated user's ID first
347
+ const userInfo = await getAuthenticatedUser(token, logger);
348
+ const userId = userInfo.data.id;
349
+ let response = await deleteRequest({
317
350
  token,
318
- uri: 'favorites/destroy.json?id=' + (0, _externalIdHelpers.removePrefix)(externalId),
351
+ uri: `https://api.x.com/2/users/${userId}/likes/${(0, _externalIdHelpers.removePrefix)(externalId)}`,
352
+ attachUrlPrefix: false,
353
+ convertPayloadToUri: false,
319
354
  logger
320
355
  });
321
- if (response.data.favorited !== undefined && response.resp.statusCode === 200) {
356
+ if (response.data.data?.liked !== undefined && response.resp.statusCode === 200) {
322
357
  return true;
323
358
  } else {
324
359
  return false;
@@ -330,16 +365,20 @@ async function unLike(token, externalId, logger) {
330
365
  }
331
366
  async function followUser(token, profileId, logger) {
332
367
  try {
368
+ // Get the authenticated user's ID first
369
+ const userInfo = await getAuthenticatedUser(token, logger);
370
+ const userId = userInfo.data.id;
333
371
  let response = await postRequest({
334
372
  token,
335
- uri: 'friendships/create.json',
373
+ uri: `https://api.x.com/2/users/${userId}/following`,
336
374
  payload: {
337
- user_id: (0, _externalIdHelpers.removePrefix)(profileId),
338
- follow: true
375
+ target_user_id: (0, _externalIdHelpers.removePrefix)(profileId)
339
376
  },
377
+ attachUrlPrefix: false,
378
+ convertPayloadToUri: false,
340
379
  logger
341
380
  });
342
- if (response.data.following !== undefined) {
381
+ if (response.data.data?.following !== undefined) {
343
382
  return true;
344
383
  } else {
345
384
  return false;
@@ -350,15 +389,17 @@ async function followUser(token, profileId, logger) {
350
389
  }
351
390
  async function unFollowUser(token, profileId, logger) {
352
391
  try {
353
- let response = await postRequest({
392
+ // Get the authenticated user's ID first
393
+ const userInfo = await getAuthenticatedUser(token, logger);
394
+ const userId = userInfo.data.id;
395
+ let response = await deleteRequest({
354
396
  token,
355
- uri: 'friendships/destroy.json',
356
- payload: {
357
- user_id: (0, _externalIdHelpers.removePrefix)(profileId)
358
- },
397
+ uri: `https://api.x.com/2/users/${userId}/following/${(0, _externalIdHelpers.removePrefix)(profileId)}`,
398
+ attachUrlPrefix: false,
399
+ convertPayloadToUri: false,
359
400
  logger
360
401
  });
361
- if (response.data.following !== undefined) {
402
+ if (response.data.data?.following !== undefined) {
362
403
  return true;
363
404
  } else {
364
405
  return false;
@@ -369,26 +410,34 @@ async function unFollowUser(token, profileId, logger) {
369
410
  }
370
411
  async function userFollowStatus(token, profileId, logger) {
371
412
  try {
413
+ // Get the authenticated user's ID first
414
+ const userInfo = await getAuthenticatedUser(token, logger);
415
+ const userId = userInfo.data.id;
416
+
417
+ // Check if authenticated user is following the target user
418
+ // We need to check the following list with pagination support
372
419
  let response = await getRequest({
373
420
  token,
374
- uri: 'friendships/lookup.json',
421
+ uri: `https://api.x.com/2/users/${userId}/following`,
375
422
  payload: {
376
- user_id: (0, _externalIdHelpers.removePrefix)(profileId)
423
+ max_results: 1000 // Max allowed per request
377
424
  },
425
+ attachUrlPrefix: false,
426
+ convertPayloadToUri: true,
378
427
  logger
379
428
  });
380
- if (response.data.length > 0) {
381
- if (response.data[0].connections.length > 0) {
382
- for (var i = 0; i < response.data[0].connections.length; i++) {
383
- if (response.data[0].connections[i] === 'following') {
384
- return true;
385
- }
386
- }
387
- return false;
388
- } else {
389
- return false;
429
+ if (response.data?.data) {
430
+ // Check if the profileId is in the following list
431
+ const isFollowing = response.data.data.some(user => user.id === (0, _externalIdHelpers.removePrefix)(profileId));
432
+ if (isFollowing) {
433
+ return true;
390
434
  }
435
+
436
+ // If not found and there's a next page, we might need to paginate
437
+ // For now, return false if not in first page
438
+ return false;
391
439
  }
440
+ return false;
392
441
  } catch (error) {
393
442
  (0, _loggerHelpers.loggerDebug)(logger, `Error in getting user follow status: ${profileId}`, error);
394
443
  if (error.message != 'Bad Request') {
@@ -400,16 +449,58 @@ async function userFollowStatus(token, profileId, logger) {
400
449
  }
401
450
  async function getBrandUserRelationship(token, profileId, brandProfileId, logger) {
402
451
  try {
403
- let {
404
- data: {
405
- relationship
406
- } = {}
407
- } = await getRequest({
408
- token,
409
- uri: `friendships/show.json?source_id=${brandProfileId}&target_id=${(0, _externalIdHelpers.removePrefix)(profileId)}`,
410
- logger
411
- });
412
- return relationship;
452
+ // In v2 API, we need to make separate calls to check following/followers status
453
+ // Check if brand follows user
454
+ let brandFollowsUser = false;
455
+ let userFollowsBrand = false;
456
+ try {
457
+ const followingResponse = await getRequest({
458
+ token,
459
+ uri: `https://api.x.com/2/users/${brandProfileId}/following`,
460
+ payload: {
461
+ max_results: 1000
462
+ },
463
+ attachUrlPrefix: false,
464
+ convertPayloadToUri: true,
465
+ logger
466
+ });
467
+ if (followingResponse.data?.data) {
468
+ brandFollowsUser = followingResponse.data.data.some(user => user.id === (0, _externalIdHelpers.removePrefix)(profileId));
469
+ }
470
+ } catch (e) {
471
+ (0, _loggerHelpers.loggerWarn)(logger, 'Error checking if brand follows user', e);
472
+ }
473
+ try {
474
+ const followersResponse = await getRequest({
475
+ token,
476
+ uri: `https://api.x.com/2/users/${brandProfileId}/followers`,
477
+ payload: {
478
+ max_results: 1000
479
+ },
480
+ attachUrlPrefix: false,
481
+ convertPayloadToUri: true,
482
+ logger
483
+ });
484
+ if (followersResponse.data?.data) {
485
+ userFollowsBrand = followersResponse.data.data.some(user => user.id === (0, _externalIdHelpers.removePrefix)(profileId));
486
+ }
487
+ } catch (e) {
488
+ (0, _loggerHelpers.loggerWarn)(logger, 'Error checking if user follows brand', e);
489
+ }
490
+
491
+ // Build a relationship object similar to v1.1 format
492
+ return {
493
+ source: {
494
+ id_str: brandProfileId,
495
+ following: brandFollowsUser,
496
+ followed_by: userFollowsBrand
497
+ },
498
+ target: {
499
+ id_str: (0, _externalIdHelpers.removePrefix)(profileId),
500
+ following: userFollowsBrand,
501
+ followed_by: brandFollowsUser
502
+ }
503
+ };
413
504
  } catch (error) {
414
505
  (0, _loggerHelpers.loggerDebug)(logger, `Error in getting user brand friendship info: ${profileId} and ${brandProfileId}`, error);
415
506
  }
@@ -441,34 +532,21 @@ async function reply(token, text, attachment, inReplyToId, removeInReplyToId, lo
441
532
  return publishTweet(token, payload, query, false, logger);
442
533
  }
443
534
  async function privateMessage(token, text, attachment, profileId, logger) {
444
- let query = 'direct_messages/events/new.json';
535
+ let query = `https://api.x.com/2/dm_conversations/with/${(0, _externalIdHelpers.removePrefix)(profileId)}/messages`;
445
536
  let mediaId;
446
537
  if (attachment) {
447
538
  // discussionType only matters if DM or Not
448
539
  mediaId = await uploadMedia(attachment, token, 'dm', logger);
449
540
  }
450
541
  const payload = {
451
- event: {
452
- type: 'message_create',
453
- message_create: {
454
- target: {
455
- recipient_id: (0, _externalIdHelpers.removePrefix)(profileId)
456
- },
457
- message_data: {
458
- text: text || ' ',
459
- ...(attachment && {
460
- attachment: {
461
- type: 'media',
462
- media: {
463
- id: mediaId
464
- }
465
- }
466
- })
467
- }
468
- }
469
- }
542
+ text: text || ' ',
543
+ ...(attachment && {
544
+ attachments: [{
545
+ media_id: mediaId
546
+ }]
547
+ })
470
548
  };
471
- return publishTweet(token, payload, query, true, logger);
549
+ return publishDirectMessage(token, payload, query, logger);
472
550
  }
473
551
  async function retweetWithComment(token, text, attachment, logger) {
474
552
  let mediaId;
@@ -487,6 +565,84 @@ async function retweetWithComment(token, text, attachment, logger) {
487
565
  let query = 'https://api.twitter.com/2/tweets';
488
566
  return publishTweet(token, payload, query, false, logger);
489
567
  }
568
+
569
+ /**
570
+ * Normalizes Twitter API responses to ensure consistent structure across v1.1 and v2
571
+ * This is critical for backward compatibility - consumers expect certain fields to exist
572
+ *
573
+ * @param {Object} response - The response object to normalize
574
+ * @param {string} responseType - Type of response: 'dm' for direct messages, 'tweet' for tweets
575
+ * @param {Object} logger - Logger instance for debugging
576
+ * @param {Object} fullResponse - Full API response including includes (for extracting author_id from expansions)
577
+ * @returns {Object} Normalized response with consistent field structure
578
+ */
579
+ function normalizeTwitterResponse(response, responseType, logger) {
580
+ let fullResponse = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
581
+ if (!response || !response.data) {
582
+ return response;
583
+ }
584
+ (0, _loggerHelpers.loggerDebug)(logger, `Normalizing Twitter API response (${responseType})`, {
585
+ beforeNormalization: JSON.stringify(response.data)
586
+ });
587
+
588
+ // Handle Twitter API v2 Direct Message responses
589
+ // v2 DM structure: { dm_event_id, dm_conversation_id }
590
+ // v1.1 expected structure: { id, author_id }
591
+ if (responseType === 'dm' && response.data.dm_event_id) {
592
+ const normalizedData = {
593
+ ...response.data,
594
+ // Add v1.1-compatible fields for backward compatibility
595
+ id: response.data.dm_event_id,
596
+ // Note: author_id is intentionally not set here. The caller (twitterApi.client.js)
597
+ // will set the correct author_id from the original document since dm_conversation_id
598
+ // doesn't reliably indicate the sender (it's just "user1-user2" format)
599
+ author_id: null
600
+ };
601
+ (0, _loggerHelpers.loggerInfo)(logger, `Normalized Twitter API v2 DM response to v1.1 format`, {
602
+ dm_event_id: response.data.dm_event_id,
603
+ normalized_id: normalizedData.id,
604
+ dm_conversation_id: response.data.dm_conversation_id,
605
+ normalized_author_id: normalizedData.author_id
606
+ });
607
+ return {
608
+ ...response,
609
+ data: normalizedData
610
+ };
611
+ }
612
+
613
+ // Handle Twitter API v2 Tweet responses
614
+ // v2 has 'id' field but author_id is in expansions (includes.users)
615
+ if (responseType === 'tweet' && response.data.id) {
616
+ const normalizedData = {
617
+ ...response.data
618
+ };
619
+
620
+ // If author_id is missing but we have includes.users from expansions
621
+ if (!normalizedData.author_id && fullResponse?.includes?.users?.[0]?.id) {
622
+ normalizedData.author_id = fullResponse.includes.users[0].id;
623
+ (0, _loggerHelpers.loggerInfo)(logger, `Added author_id to tweet response from expansions`, {
624
+ tweet_id: normalizedData.id,
625
+ author_id: normalizedData.author_id
626
+ });
627
+ } else if (normalizedData.author_id) {
628
+ (0, _loggerHelpers.loggerDebug)(logger, `Twitter v2 tweet response already has 'author_id'`, {
629
+ id: response.data.id,
630
+ author_id: normalizedData.author_id
631
+ });
632
+ } else {
633
+ (0, _loggerHelpers.loggerWarn)(logger, `Twitter v2 tweet response missing author_id and no expansions available`, {
634
+ id: response.data.id,
635
+ hasIncludes: !!fullResponse?.includes,
636
+ hasUsers: !!fullResponse?.includes?.users
637
+ });
638
+ }
639
+ return {
640
+ ...response,
641
+ data: normalizedData
642
+ };
643
+ }
644
+ return response;
645
+ }
490
646
  async function publishTweet(token, payload, query, isDirectMessage, logger) {
491
647
  try {
492
648
  let nativeResponse = await postRequest({
@@ -521,12 +677,50 @@ async function publishTweet(token, payload, query, isDirectMessage, logger) {
521
677
  (0, _loggerHelpers.loggerDebug)(logger, `Twitter the data response is`, {
522
678
  response: JSON.stringify(response)
523
679
  });
524
- return response;
680
+
681
+ // Normalize the response to ensure backward compatibility with v1.1 structure
682
+ // Pass full nativeResponse to extract author_id from expansions if needed
683
+ const normalizedResponse = normalizeTwitterResponse(response, 'tweet', logger, nativeResponse.data // Full response with includes.users for author_id
684
+ );
685
+ return normalizedResponse;
525
686
  } catch (err) {
526
687
  (0, _loggerHelpers.loggerError)(logger, `Twitter publish exception details`, err);
527
688
  throw err;
528
689
  }
529
690
  }
691
+ async function publishDirectMessage(token, payload, query, logger) {
692
+ try {
693
+ let nativeResponse = await postRequest({
694
+ token,
695
+ uri: query,
696
+ payload,
697
+ convertPayloadToUri: false,
698
+ attachUrlPrefix: false
699
+ });
700
+ (0, _loggerHelpers.loggerInfo)(logger, `finished sending DM via Twitter API v2`, {
701
+ data: JSON.stringify(nativeResponse.data)
702
+ });
703
+ const response = nativeResponse.resp ? {
704
+ statusCode: nativeResponse.resp.statusCode,
705
+ statusMessage: nativeResponse.resp.statusCode === 201 || nativeResponse.resp.statusCode === 200 ? 'Success' : 'Unknown Failure',
706
+ data: nativeResponse.data.data
707
+ } : {
708
+ statusCode: nativeResponse.statusCode,
709
+ statusMessage: nativeResponse.message
710
+ };
711
+ (0, _loggerHelpers.loggerDebug)(logger, `Twitter DM response is`, {
712
+ response: JSON.stringify(response)
713
+ });
714
+
715
+ // Normalize the response to ensure backward compatibility with v1.1 structure
716
+ // This is CRITICAL - v2 DM responses have dm_event_id instead of id
717
+ const normalizedResponse = normalizeTwitterResponse(response, 'dm', logger);
718
+ return normalizedResponse;
719
+ } catch (err) {
720
+ (0, _loggerHelpers.loggerError)(logger, `Twitter DM exception details`, err);
721
+ throw err;
722
+ }
723
+ }
530
724
  async function postRequest(_ref2) {
531
725
  let {
532
726
  token,
@@ -685,32 +879,177 @@ function generateFilePath(mimeType, dirname) {
685
879
  }
686
880
  // local
687
881
  async function uploadMedia(attachment, token, discussionType, logger) {
688
- return new Promise(async (resolve, reject) => {
689
- let filePath = generateFilePath(attachment.mimeType, attachment.__dirname);
882
+ let filePath;
883
+ try {
884
+ filePath = generateFilePath(attachment.mimeType, attachment.__dirname);
690
885
  await downloadImage(attachment.url, filePath);
691
- const T = new _socialTwit.default({
692
- consumer_key: token.consumer_key,
693
- consumer_secret: token.consumer_secret,
694
- access_token: token.token,
695
- access_token_secret: token.tokenSecret
886
+ const mediaCategory = discussionType === 'dm' ? 'dm_image' : 'tweet_image';
887
+ const fileStats = _fs.default.statSync(filePath);
888
+ const fileSize = fileStats.size;
889
+ const fileData = _fs.default.readFileSync(filePath);
890
+
891
+ // For small files, use simple upload
892
+ if (fileSize < 5000000) {
893
+ // 5MB
894
+ const mediaIdString = await simpleMediaUpload(fileData, attachment.mimeType, token, logger);
895
+ removeMedia(filePath, logger);
896
+ return mediaIdString;
897
+ } else {
898
+ // For large files, use chunked upload
899
+ const mediaIdString = await chunkedMediaUpload(filePath, fileSize, attachment.mimeType, mediaCategory, token, logger);
900
+ removeMedia(filePath, logger);
901
+ return mediaIdString;
902
+ }
903
+ } catch (e) {
904
+ (0, _loggerHelpers.loggerError)(logger, `Failed uploading media`, e);
905
+ if (filePath) {
906
+ removeMedia(filePath, logger);
907
+ }
908
+ throw e;
909
+ }
910
+ }
911
+
912
+ // Simple upload for small media files
913
+ async function simpleMediaUpload(fileData, mimeType, token, logger) {
914
+ try {
915
+ const base64Data = fileData.toString('base64');
916
+ const mediaType = mimeType.split('/')[0]; // 'image', 'video', etc.
917
+
918
+ const url = 'https://upload.twitter.com/1.1/media/upload.json';
919
+ const {
920
+ Authorization
921
+ } = token.oauth.toHeader(token.oauth.authorize({
922
+ url,
923
+ method: 'post',
924
+ data: {}
925
+ }, {
926
+ key: token.token,
927
+ secret: token.tokenSecret
928
+ }));
929
+ const result = await _superagent.default.post(url).set('Authorization', Authorization).send({
930
+ media_data: base64Data,
931
+ media_type: mimeType
696
932
  });
697
- let mediaCategory = discussionType === 'dm' ? 'dm' : 'tweet';
698
- try {
699
- T.postMediaChunked({
700
- file_path: filePath
701
- }, mediaCategory, function (err, data, response) {
702
- if (err) {
703
- reject(err);
704
- }
705
- resolve(data.media_id_string);
706
- removeMedia(filePath, logger);
933
+ (0, _loggerHelpers.loggerDebug)(logger, 'Media uploaded successfully (simple)', {
934
+ media_id: result.body.media_id_string
935
+ });
936
+ return result.body.media_id_string;
937
+ } catch (e) {
938
+ (0, _loggerHelpers.loggerError)(logger, 'Error in simple media upload', e);
939
+ throw e;
940
+ }
941
+ }
942
+
943
+ // Chunked upload for large media files
944
+ async function chunkedMediaUpload(filePath, fileSize, mimeType, mediaCategory, token, logger) {
945
+ try {
946
+ const mediaType = mimeType.split('/')[0];
947
+
948
+ // Step 1: INIT
949
+ const initResponse = await mediaUploadRequest({
950
+ command: 'INIT',
951
+ total_bytes: fileSize,
952
+ media_type: mimeType,
953
+ media_category: mediaCategory
954
+ }, token, logger);
955
+ const mediaId = initResponse.media_id_string;
956
+ (0, _loggerHelpers.loggerDebug)(logger, 'Media upload INIT successful', {
957
+ mediaId
958
+ });
959
+
960
+ // Step 2: APPEND (upload in chunks)
961
+ const chunkSize = 5 * 1024 * 1024; // 5MB chunks
962
+ const fileBuffer = _fs.default.readFileSync(filePath);
963
+ let segmentIndex = 0;
964
+ for (let offset = 0; offset < fileSize; offset += chunkSize) {
965
+ const chunk = fileBuffer.slice(offset, Math.min(offset + chunkSize, fileSize));
966
+ const base64Chunk = chunk.toString('base64');
967
+ await mediaUploadRequest({
968
+ command: 'APPEND',
969
+ media_id: mediaId,
970
+ media_data: base64Chunk,
971
+ segment_index: segmentIndex
972
+ }, token, logger);
973
+ (0, _loggerHelpers.loggerDebug)(logger, `Media chunk ${segmentIndex} uploaded`, {
974
+ mediaId,
975
+ segmentIndex
707
976
  });
708
- } catch (e) {
709
- (0, _loggerHelpers.loggerError)(logger, `Failed posting media`, e);
710
- // this is just a safety precaution
711
- removeMedia(filePath, logger);
977
+ segmentIndex++;
712
978
  }
713
- });
979
+
980
+ // Step 3: FINALIZE
981
+ const finalizeResponse = await mediaUploadRequest({
982
+ command: 'FINALIZE',
983
+ media_id: mediaId
984
+ }, token, logger);
985
+ (0, _loggerHelpers.loggerDebug)(logger, 'Media upload FINALIZE successful', {
986
+ mediaId
987
+ });
988
+
989
+ // Step 4: Check processing status if needed
990
+ if (finalizeResponse.processing_info) {
991
+ await waitForMediaProcessing(mediaId, token, logger);
992
+ }
993
+ return mediaId;
994
+ } catch (e) {
995
+ (0, _loggerHelpers.loggerError)(logger, 'Error in chunked media upload', e);
996
+ throw e;
997
+ }
998
+ }
999
+
1000
+ // Make a media upload request
1001
+ async function mediaUploadRequest(params, token, logger) {
1002
+ const url = 'https://upload.twitter.com/1.1/media/upload.json';
1003
+ const {
1004
+ Authorization
1005
+ } = token.oauth.toHeader(token.oauth.authorize({
1006
+ url,
1007
+ method: 'post',
1008
+ data: {}
1009
+ }, {
1010
+ key: token.token,
1011
+ secret: token.tokenSecret
1012
+ }));
1013
+ try {
1014
+ const result = await _superagent.default.post(url).set('Authorization', Authorization).send(params);
1015
+ return result.body;
1016
+ } catch (e) {
1017
+ (0, _loggerHelpers.loggerError)(logger, `Media upload request failed for command: ${params.command}`, e);
1018
+ throw e;
1019
+ }
1020
+ }
1021
+
1022
+ // Wait for media processing to complete
1023
+ async function waitForMediaProcessing(mediaId, token, logger) {
1024
+ let maxAttempts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 20;
1025
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
1026
+ const statusResponse = await mediaUploadRequest({
1027
+ command: 'STATUS',
1028
+ media_id: mediaId
1029
+ }, token, logger);
1030
+ if (!statusResponse.processing_info) {
1031
+ return; // Processing complete
1032
+ }
1033
+ const state = statusResponse.processing_info.state;
1034
+ if (state === 'succeeded') {
1035
+ (0, _loggerHelpers.loggerDebug)(logger, 'Media processing succeeded', {
1036
+ mediaId
1037
+ });
1038
+ return;
1039
+ }
1040
+ if (state === 'failed') {
1041
+ throw new Error(`Media processing failed: ${statusResponse.processing_info.error?.message || 'Unknown error'}`);
1042
+ }
1043
+
1044
+ // Still processing, wait before checking again
1045
+ const waitTime = statusResponse.processing_info.check_after_secs || 1;
1046
+ (0, _loggerHelpers.loggerDebug)(logger, `Media still processing, waiting ${waitTime}s`, {
1047
+ mediaId,
1048
+ state
1049
+ });
1050
+ await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
1051
+ }
1052
+ throw new Error('Media processing timeout');
714
1053
  }
715
1054
  function removeMedia(file, logger) {
716
1055
  try {
@@ -722,5 +1061,4 @@ function removeMedia(file, logger) {
722
1061
  } catch (e) {
723
1062
  (0, _loggerHelpers.loggerError)(logger, `failed trying to remove media ${file} it may have already been removed`);
724
1063
  }
725
- }
726
- ;
1064
+ }
@@ -31,8 +31,7 @@ var TwitterNative = _interopRequireWildcard(require("./http/twitter.native.js"))
31
31
  var InstagramNative = _interopRequireWildcard(require("./http/instagram.native.js"));
32
32
  var ThreadsNative = _interopRequireWildcard(require("./http/threads.native.js"));
33
33
  var AssetManagerClient = _interopRequireWildcard(require("./http/assetManager.client.js"));
34
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
35
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
34
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
36
35
  const DocumentHelperFunctions = {
37
36
  ...messageHelpers,
38
37
  ...applicationTagFunctions,