needle icon indicating copy to clipboard operation
needle copied to clipboard

Twitter API v1 stream not working with needle in Node JS (getting no data)

Open frzsombor opened this issue 3 years ago • 3 comments

In my project I'm using Twitter API user timeline (both v1 and v2) and filtered stream (v2) with needle successfully, but I can't get the v1 version of filtered stream (statuses/filter) working with needle.

I feel clueless, as needle's streaming works fine with API v2 (just like the official example) and I've tested the authentication part (OAuth) of my v1 code and verified that it works well (response code: 200) - yet I'm not getting any data from the stream.

I even tried finding the problem by setting up Fiddler as a proxy of needle, but I saw nothing strange.

I've created an extracted, stand alone version of the v1 streaming module, hoping that someone can spot the issue/bug.

const uuid = require('uuid');
const oauth = require('oauth-sign');
const needle = require('needle');

const keys = {
    twitter: {
        consumerKey: '<your-consumerKey>', //API Key
        consumerSecretKey: '<your-consumerSecretKey>', //API Secret Key
        oauthToken: '<your-oauthToken>', //Access Token
        oauthTokenSecret: '<your-oauthTokenSecret>', //Access Token Secret
    }
};

const streamURL = 'https://stream.twitter.com/1.1/statuses/filter.json';

const apiParams = {
    'track': 'pizza',
};

function getAuthHeader() {
    const timestamp = Date.now() / 1000;
    const nonce = uuid.v4().replace(/-/g, '');

    let oauthParams = {
        'oauth_consumer_key': keys.twitter.consumerKey,
        'oauth_nonce': nonce,
        'oauth_signature_method': 'HMAC-SHA1',
        'oauth_timestamp': timestamp,
        'oauth_token': keys.twitter.oauthToken,
        'oauth_version': '1.0'
    };

    let mergedParams = { ...apiParams, ...oauthParams };

    oauthParams['oauth_signature'] = oauth.hmacsign(
        'POST', streamURL, mergedParams, 
        keys.twitter.consumerSecretKey, 
        keys.twitter.oauthTokenSecret
    );

    return Object.keys(oauthParams).sort().map(function (k) {
        return k + '="' + oauth.rfc3986(oauthParams[k]) + '"';
    }).join(', ');
}

function streamConnect(retryAttempt) {
    const stream = needle.post(streamURL, apiParams, {
        headers: {
            'Authorization': `OAuth ${getAuthHeader()}`,
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        timeout: 20000
    });

    stream.on('header', (statusCode, headers) => {
        console.log(
            `Status: ${statusCode} ` + 
            `(${statusCode === 200 ? 'OK' : 'ERROR'})`
        );
    }).on('data', data => {
        //never receiving any data ???
        console.log('data received:');
        console.log(data);
    }).on('err', error => {
        // This reconnection logic will attempt to reconnect when a disconnection is detected.
        // To avoid rate limits, this logic implements exponential backoff, so the wait time
        // will increase if the client cannot reconnect to the stream. 
        let retryTimeout = 2 ** retryAttempt;
        console.warn(`A connection error occurred: ${error.message}. Reconnecting in ${retryTimeout/1000} seconds...`)
        setTimeout(() => {
            streamConnect(++retryAttempt);
        }, retryTimeout);
    });

    return stream;
}

streamConnect(0);

frzsombor avatar Jul 19 '21 22:07 frzsombor

Hi @frzsombor, what version of Node and Needle are your running? IIRC there was an issue with the nonce that was fixed a few releases ago.

tomas avatar Aug 27 '21 13:08 tomas

I'm using Node v14.15.5 and I've tried v2.9.0 and v3.0.0 too, but none of them worked with this test script. Still don't know why, as this code works with Twitter API v2 and other libraries still work with the v1.1 API.

frzsombor avatar Aug 31 '21 11:08 frzsombor

Here is my code I'm using V2 of the API, hope this helps:

https://github.com/chase-app/basic-twitter-server

mfreeman451 avatar Mar 03 '22 16:03 mfreeman451