Using Pipedream OAuth with Tweepy

I’m trying to connect Tweepy using the authentication details obtained from pd.inputs["twitter"]["$auth"] but every request I’ve made has come back as unauthenticated even though the example authentication script to verify_credentials.json works.

I’m assuming Pipedream uses 3-legged OAuth? I’ve tried almost every combination of authentication available Authentication — tweepy 4.10.0 documentation

Hi @noble

First off, welcome to the Pipedream community. Happy to have you!

It could be an issue of the access token passed into the wrong variable name or dictionary key. Do you mind sharing your implementation of the Tweepy code that’s throwing the 401 error?

Adding onto the base code, I’ve tried the following:

import requests
import tweepy

def handler(pd: "pipedream"):
  request_data = { "method": "GET", "url": "https://api.twitter.com/1.1/account/verify_credentials.json"  }
  token = { "key": pd.inputs["twitter"]["$auth"]["oauth_access_token"], "secret": pd.inputs["twitter"]["$auth"]["oauth_refresh_token"] }
  data = { "requestData": request_data, "token": token  }
  auth_data = requests.post(pd.inputs["twitter"]["$auth"]["oauth_signer_uri"], json=data)
  headers = {"Authorization": auth_data.text}
  r = requests.get("https://api.twitter.com/1.1/account/verify_credentials.json", headers=headers)

   # Added 
  auth = tweepy.OAuth1UserHandler(
        pd.inputs["twitter"]["$auth"]["oauth_access_token"], pd.inputs["twitter"]["$auth"]["oauth_refresh_token"],
        callback=pd.inputs["twitter"]["$auth"]["oauth_signer_uri"]
    )
  api = tweepy.API(auth)
  user = api.get_user(screen_name='twitter')
  print(user.screen_name)

Also tried token only:

client = tweepy.Client(bearer_token=pd.inputs["twitter"]["$auth"]["oauth_access_token"])

I looked at Pipedream’s docs and I believe everything uses v1, but I still tried these oauth2 just to be sure:

auth = tweepy.OAuth2BearerHandler(pd.inputs["twitter"]["$auth"]["oauth_refresh_token"])

And

auth = tweepy.OAuth2BearerHandler(pd.inputs["twitter"]["$auth"]["oauth_access_token"])

Thank you for your help @pierce

Hi @noble,

Thanks for the thorough code share, much easier to follow along.

To confirm your assumption - yes the Pipedream Twitter app is using the Twitter v1.1 API. The tweepy.Client is using Twitter v2, so unfortunately that’s not compatible.

I searched through the Tweepy documentation, and unfortunately it seems like there’s not an officially documented way of bypassing their built in OAuth flow and just providing your access token managed by Pipedream’s OAuth flow isn’t supported.

Would it be feasible to instead modify the requests API call to verify the credentials and instead make your Twitter API call directly?

Is there a specific reason you’re looking to use Tweepy?

Thanks for helping @pierce

The reason I’m using Tweepy is because I have some existing code that uses Tweepy and I’m not experienced with making raw request based API calls, so I always lean on a lib that abstracts away the complexity.

For instance, I found some example code to upload an image to twitter with python + requests and it looked like a nightmare.

I’ve used Tweepy with v1 api, but I guess I was using a different type of auth.

Ah yes, I know exactly what you mean @noble.

I found a limitation with the Twitter proxy that Pipedream uses under the hood to send authenticated requests to Twitter.

I needed to upload images to tweets to, so I wrote a component to upload the image with the Twitter Developer App on Pipedream:

import Twit from 'twit';
import fs from 'fs';

export default defineComponent({
  props: {
    twitter_developer_app: {
      type: "app",
      app: "twitter_developer_app",
    },
    altText: {
      type: 'string',
      label: 'altText',
    },
    filePath: {
       type: 'string',
       label: 'Path to file',
       description: 'The media file to upload to Twitter from the `/tmp` directory, for example `/tmp/image.png`'
    }
  },
  async run({steps, $}) {
    const { api_key, api_secret_key, access_token, access_token_secret } = this.twitter_developer_app.$auth
    
    const T = new Twit({
      consumer_key: api_key,
      consumer_secret: api_secret_key,
      access_token,
      access_token_secret,
      timeout_ms: 60 * 1000,  // optional HTTP request timeout to apply to all requests.
      strictSSL: true,  // optional - requires SSL certificates to be valid.
    })
    
    var b64content = fs.readFileSync(this.filePath, { encoding: 'base64' })
 
    // first we must post the media to Twitter
    const { data } =  await T.post('media/upload', { media_data: b64content });

    // now we can assign alt text to the media, for use by screen readers and
    // other text-based presentations and interpreters
    const mediaIdStr = data.media_id_string
  
    const meta_params = { media_id: mediaIdStr, alt_text: { text: this.altText } };
    const mediaCreateResponse = await T.post('media/metadata/create', meta_params);
    $.export('media_id_string', mediaIdStr);
    $.export('media_upload_response', mediaUploadResponse);
    $.export('media_create_response', mediaCreateResponse);
  },
})

Then I was able to use the media_create_response in a later step to actually include the image in a tweet:

module.exports = defineComponent({
  props: {
    twitter_developer_app: {
      type: "app",
      app: "twitter_developer_app",
    }
  },
  async run({steps, $}) {
    const Twit = require('twit')
    
    const { api_key, api_secret_key, access_token, access_token_secret } = this.twitter_developer_app.$auth
    
    const T = new Twit({
      consumer_key: api_key,
      consumer_secret: api_secret_key,
      access_token,
      access_token_secret,
      timeout_ms: 60 * 1000,  // optional HTTP request timeout to apply to all requests.
      strictSSL: true,  // optional - requires SSL certificates to be valid.
    })
    
    await T.post('statuses/update', { 
      status: `Good morning Cleveland! 🌆 \n Lake Erie's surface temp is ${steps.convert_lake_erie_to_faren.$return_value}°F.`,
      media_ids: steps.upload_image.$return_value.media_id_string
    })
  },
}

You don’t need to use Javascript in your implementation, you can mix and match between languages on Pipedream.

So you could just use my example media upload code step, then use Tweepy in your second.