79700092

Date: 2025-07-13 16:24:44
Score: 0.5
Natty:
Report link

in reference to @Sumit Mahajan 's implementation, twitter has had some updates to their media upload via v2 api.They've now removed the COMMAND= param, and separated the end points for initialize, append and finalize. (i've updated sumit's implementation with the new api endpoints, and used an s3 asset)

https://devcommunity.x.com/t/media-upload-endpoints-update-and-extended-migration-deadline/241818

export const TWITTER_ENDPOINTS = {
  TWITTER_TWEET_URL: "https://api.twitter.com/2/tweets",
  TWITTER_MEDIA_INITIALIZE: "https://api.twitter.com/2/media/upload/initialize",
  TWITTER_MEDIA_APPEND: "https://api.twitter.com/2/media/upload/{id}/append",
  TWITTER_MEDIA_FINALIZE: "https://api.twitter.com/2/media/upload/{id}/finalize",
  TWITTER_MEDIA_STATUS: "https://api.twitter.com/2/media/upload"
} 

const awsMediaResponse = await s3.getObject({
        Bucket: bucket,
        Key: `path/to/s3_file.mp4`,
      }).promise();
    if (!awsMediaResponse.Body) throw new Error("No Body returned from s3 object.");

   
const tokenResponse = await getValidTwitterAccessToken();
     
const buffer = Buffer.isBuffer(awsMediaResponse.Body) ? awsMediaResponse.Body : Buffer.from(awsMediaResponse.Body as Uint8Array);
const totalBytes = buffer.length;
const mediaUint8 = new Uint8Array(buffer);
const contentType = awsMediaResponse.ContentType;
const CHUNK_SIZE = Math.min(2 * 1024 * 1024, totalBytes);

const initResponse = await fetch(TWITTER_ENDPOINTS.TWITTER_MEDIA_INITIALIZE, {
          method: "POST",
          headers: {
            Authorization: `Bearer ${tokenResponse.twitterAccessToken}`,
            "Content-Type": "application/json"
          },
          body: JSON.stringify({
            media_category: "tweet_video",
            media_type: contentType,
            total_bytes: totalBytes
          })
});
if (!initResponse.ok) throw new Error(`Failed to initialize media upload: ${await initResponse.text()}`);

      const initData = await initResponse.json();
      const mediaId = initData.data.id;
      let segmentIndex = 0;
      console.log("total: ", totalBytes, "chunk size: ", CHUNK_SIZE);
      if (totalBytes <= CHUNK_SIZE) {
        const appendFormData = new FormData();
        appendFormData.append("media", new Blob([mediaUint8]));
        appendFormData.append("segment_index", segmentIndex.toString())
        const appendResponse = await fetch(TWITTER_ENDPOINTS.TWITTER_MEDIA_APPEND.replace("{id}", mediaId), {
            method: "POST",
            headers: {
              Authorization: `Bearer ${tokenResponse.twitterAccessToken}`,
              "Content-Type": "multipart/form-data"
            },
            body: appendFormData,
          }
        );
        if (!appendResponse.ok) throw new Error(`Failed to append single chunk media: ${await appendResponse.text()}`)
      } else {
        for (let byteIndex = 0; byteIndex < totalBytes; byteIndex += CHUNK_SIZE) {
          const chunk = mediaUint8.slice(
            byteIndex,
            Math.min(byteIndex + CHUNK_SIZE, totalBytes)
          );
          const appendFormData = new FormData();
          appendFormData.append("media", new Blob([chunk]));
          appendFormData.append("segment_index", segmentIndex.toString())

          const appendResponse = await fetch(TWITTER_ENDPOINTS.TWITTER_MEDIA_APPEND.replace("{id}", mediaId), {
              method: "POST",
              headers: {
                Authorization: `Bearer ${tokenResponse.twitterAccessToken}`
              },
              body: appendFormData,
            }
          );

          if (!appendResponse.ok) throw new Error(`Failed to append media chunk ${segmentIndex}: ${await appendResponse.text()}`);
          segmentIndex++;
        }
      }
      
      const finalizeResponse = await fetch(TWITTER_ENDPOINTS.TWITTER_MEDIA_FINALIZE.replace("{id}", mediaId), {
          method: "POST",
          headers: {
            Authorization: `Bearer ${tokenResponse.twitterAccessToken}`,
          },
        }
      );
      if (!finalizeResponse.ok) throw new Error(`Failed to finalize media upload: ${await finalizeResponse.text()}`);
      await checkMediaStatus(tokenResponse.twitterAccessToken, mediaId);
      console.log("status check: ", mediaId);

      const tweetPostResponse = await axios({
        url: TWITTER_ENDPOINTS.TWITTER_TWEET_URL,
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${tokenResponse.twitterAccessToken}`
        },
        data: {
          "text": caption,
          "media": {
            "media_ids": [mediaId]
          } 
        }
      })

in addition, the status check function the response has the processing_info in the data object now for twitter:

const statusData = await statusResponse.json();
processingInfo = statusData.data.processing_info;
Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • User mentioned (1): @Sumit
  • Low reputation (1):
Posted by: Logan