Advice for uploading media to Twitter in V2

Hi, I’m looking to upload media to twitter using a V2 workflow. It looks like this was integrated in V1 and is now unavailable. Does anyone know how this can be done?

Here’s an example V1 workflow:

Thanks for any help/thoughts.

Hi @hanjoyoutaku,

Unfortunately we don’t have v2 workflow sharing yet but here’s my take at recreating it in v2.

Please let me know if it works for you!

import axios from 'axios';
import { axios as platformAxios } from '@pipedream/platform';

// To use previous step data, pass the `steps` object to the run() function
export default defineComponent({
  props: {
    twitter: {
      type: 'app',
      app: 'twitter'
    },
    url: {
      type: 'string',
      label: "Photo URL",
      description: 'Full URL to the image to download then upload to a Tweet',
    },
    media_type: {
      type: 'string',
      label: "Media Type",
      description: 'The MIME type of the media being uploaded. See https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init'
    }
  },
  async run({ steps, $ }) {
    // Data we need to make the Twitter API request
    const oauthSignerUri = this.twitter.$auth.oauth_signer_uri
    const token = {
      key: this.twitter.$auth.oauth_access_token,
      secret: this.twitter.$auth.oauth_refresh_token,
    }
    const signConfig = {
      token,
      oauthSignerUri
    }

    // Download image, base64 encode it, then upload the media to Twitter
    const imageResponse = await axios({
      url: this.url,
      method: "GET",
      responseType: "arraybuffer"
    })

    const file = Buffer.from(imageResponse.data, 'binary')
    const total_bytes = file.length
    const base64EncodedFile = file.toString('base64')
    const media_type = this.media_type;

    // First, tell Twitter the type of file you're uploading, how big it is, etc.
    // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init
    const mediaUploadInitRequest = {
      method: 'POST',
      data: '',
      url: "https://upload.twitter.com/1.1/media/upload.json",
      params: {
        command: "INIT",
        total_bytes,
        media_type,
      }
    }

    // Twitter returns a media_id_string that we use to upload the file,
    // and to reference it in future steps
    const uploadMediaInitResponse = await platformAxios($, mediaUploadInitRequest, signConfig)
    $.export('uploadMediaInitResponse', uploadMediaInitResponse);
    const mediaIdString = uploadMediaInitResponse.media_id_string;
    $.export('mediaIdString', mediaIdString);

    // Split the file into chunks, APPEND each chunk
    const splitStringRe = new RegExp('.{1,' + 1000 + '}', 'g');
    const chunks = base64EncodedFile.match(splitStringRe);

    for (const [segment_index, media_data] of chunks.entries()) {
      console.log(`Processing chunk ${segment_index}`)
      // APPEND file content in chunks
      // See https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append
      const mediaUploadAppendRequest = {
        method: 'POST',
        data: '',
        url: "https://upload.twitter.com/1.1/media/upload.json",
        params: {
          command: "APPEND",
          media_id: this.mediaIdString,
          segment_index,
          media_data,
        }
      }
      await platformAxios($, mediaUploadAppendRequest, signConfig)
    }

    // Finally, tell Twitter we're done uploading
    // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize
    const mediaUploadFinalizeRequest = {
        method: 'POST',
        data: '',
        url: "https://upload.twitter.com/1.1/media/upload.json",
        params: {
          command: "FINALIZE",
          media_id: this.mediaIdString,
        }
      }
      await platformAxios($, mediaUploadFinalizeRequest, signConfig)
  },
})

Hi pierce, thank you for your help.

At the moment it seems like Twitter is returning error 400, “media_id or media_key parameter is missing.”.

If you have any clues as to why, will be appreciated.

Small v1 to v2 migration mistake on my part. The API call was including the wrong variable name.

import axios from 'axios';
import { axios as platformAxios } from '@pipedream/platform';

// To use previous step data, pass the `steps` object to the run() function
export default defineComponent({
  props: {
    twitter: {
      type: 'app',
      app: 'twitter'
    },
    url: {
      type: 'string',
      label: "Photo URL",
      description: 'Full URL to the image to download then upload to a Tweet',
    },
    media_type: {
      type: 'string',
      label: "Media Type",
      description: 'The MIME type of the media being uploaded. See https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init'
    }
  },
  async run({ steps, $ }) {
    // Data we need to make the Twitter API request
    const oauthSignerUri = this.twitter.$auth.oauth_signer_uri
    const token = {
      key: this.twitter.$auth.oauth_access_token,
      secret: this.twitter.$auth.oauth_refresh_token,
    }
    const signConfig = {
      token,
      oauthSignerUri
    }

    // Download image, base64 encode it, then upload the media to Twitter
    const imageResponse = await axios({
      url: this.url,
      method: "GET",
      responseType: "arraybuffer"
    })

    const file = Buffer.from(imageResponse.data, 'binary')
    const total_bytes = file.length
    const base64EncodedFile = file.toString('base64')
    const media_type = this.media_type;

    // First, tell Twitter the type of file you're uploading, how big it is, etc.
    // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-init
    const mediaUploadInitRequest = {
      method: 'POST',
      data: '',
      url: "https://upload.twitter.com/1.1/media/upload.json",
      params: {
        command: "INIT",
        total_bytes,
        media_type,
      }
    }

    // Twitter returns a media_id_string that we use to upload the file,
    // and to reference it in future steps
    const uploadMediaInitResponse = await platformAxios($, mediaUploadInitRequest, signConfig)
    $.export('uploadMediaInitResponse', uploadMediaInitResponse);
    const mediaIdString = uploadMediaInitResponse.media_id_string;
    $.export('mediaIdString', mediaIdString);

    // Split the file into chunks, APPEND each chunk
    const splitStringRe = new RegExp('.{1,' + 1000 + '}', 'g');
    const chunks = base64EncodedFile.match(splitStringRe);

    for (const [segment_index, media_data] of chunks.entries()) {
      console.log(`Processing chunk ${segment_index}`)
      // APPEND file content in chunks
      // See https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-append
      const mediaUploadAppendRequest = {
        method: 'POST',
        data: '',
        url: "https://upload.twitter.com/1.1/media/upload.json",
        params: {
          command: "APPEND",
          media_id: this.mediaIdString,
          segment_index,
          media_data,
        }
      }
      await platformAxios($, mediaUploadAppendRequest, signConfig)
    }

    // Finally, tell Twitter we're done uploading
    // https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload-finalize
    const mediaUploadFinalizeRequest = {
        method: 'POST',
        data: '',
        url: "https://upload.twitter.com/1.1/media/upload.json",
        params: {
          command: "FINALIZE",
          media_id: mediaIdString,
        }
      }
      await platformAxios($, mediaUploadFinalizeRequest, signConfig)
  },
})

Replace your Node.js code with that and see if that helps!

Thanks again Pierce. Looks like we’ve gotten it all working now, but when I add my media id to the create tweet function it doesn’t add an image to the tweet - just the status. Is this a bug with the “create tweet” function in V2, or am I missing something? Here’s a screenshot (I’ve hardcoded a media id example to make sure everything is working)


The status gets posted but the image does not.

I’m playing around and don’t want to jump to any conclusions, tried some other options on the CreateTweet functionality e.g. including latitude and longitude or an attachment and those didn’t seem to work either. Maybe I’m missing something.

Thanks for helping me. I love pipedream.

I have another workflow I’m having problems with too, trying to reply using the create tweet function.

All of the parameters are included, but the tweet doesn’t seem to reply. Very open to this being a series of mistakes ha, and we can just keep our attention on the media upload. Is it possible to look at or debug the code inside of the create tweet function?

Hi @hanjoyoutaku ,

This is a quirk with the Twitter API, not sure if you saw this in their documentation but you must upload a Media instance before it can be used in a tweet:

Here’s a complete tutorial:

But I think I may see a bug in the source code of the action here: pipedream/twitter.app.mjs at 23144807af52ffb2512fe3c064e6ae76cc7c0eae · PipedreamHQ/pipedream · GitHub

The passed parameter is named mediaIds but Twitter is expected media_ids. That might be the issue.

I have opened a thread here in our public Github repo, a team member will be working on it:

I believe this bug was affecting other options like you mentioned. Please comment or react to that issue for real time updates.

Thanks so much pierce and other team members.