• Stars
    star
    794
  • Rank 57,349 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A tiny, full-featured, flexible client / server library for the Twitter API

Twitter Lite

A tiny, full-featured, modern client / server library for the Twitter API.

npm travis

Features

  • Promise driven via Async / Await
  • REST and Stream support
  • Typescript support
  • Works in Node.js
  • Rate limiting support
  • Under 1kb
  • Minimal dependencies
  • Test suite

Why

We have built this library because existing ones have not been recently maintained, or depend on outdated libraries.

Installation

yarn add twitter-lite
npm install twitter-lite

Then you can include the following at the top of your code:

import Twitter from 'twitter-lite';

const client = new Twitter({
  ...
})

client.get(...)
client.post(...)

Usage

  • Create an app on https://apps.twitter.com/
  • Grab the Consumer Key (API Key) and Consumer Secret (API Secret) from Keys and Access Tokens
  • Make sure you set the right access level for your app
  • If you want to use user-based authentication, grab the access token key and secret as well

App vs. User authentication

Twitter has two different authentication options:

  • App: higher rate limits. Great for building your own Twitter App.
  • User: lower rate limits. Great for making requests on behalf of a User.

User authentication requires:

  • consumer_key
  • consumer_secret
  • access_token_key
  • access_token_secret

App authentication requires:

  • bearer_token

App authentication is a simple header behind the scenes:

headers: {
  Authorization: `Bearer ${bearer_token}`;
}

You can get the bearer token by calling .getBearerToken().

Verifying credentials example (user auth)

const client = new Twitter({
  subdomain: "api", // "api" is the default (change for other subdomains)
  version: "1.1", // version "1.1" is the default (change for other subdomains)
  consumer_key: "abc", // from Twitter.
  consumer_secret: "def", // from Twitter.
  access_token_key: "uvw", // from your User (oauth_token)
  access_token_secret: "xyz" // from your User (oauth_token_secret)
});

client
  .get("account/verify_credentials")
  .then(results => {
    console.log("results", results);
  })
  .catch(console.error);

App authentication example

const user = new Twitter({
  consumer_key: "abc",
  consumer_secret: "def"
});

const response = await user.getBearerToken();
const app = new Twitter({
  bearer_token: response.access_token
});

Oauth authentication

According to the docs this helps you get access token from your users.

const client = new Twitter({
  consumer_key: "xyz",
  consumer_secret: "xyz"
});

client
  .getRequestToken("http://callbackurl.com")
  .then(res =>
    console.log({
      reqTkn: res.oauth_token,
      reqTknSecret: res.oauth_token_secret
    })
  )
  .catch(console.error);

Then you redirect your user to https://api.twitter.com/oauth/authenticate?oauth_token=xyz123abc, and once you get the verifier and the token, you pass them on to the next stage of the authentication.

const client = new Twitter({
  consumer_key: "xyz",
  consumer_secret: "xyz"
});

client
  .getAccessToken({
    oauth_verifier: oauthVerifier,
    oauth_token: oauthToken
  })
  .then(res =>
    console.log({
      accTkn: res.oauth_token,
      accTknSecret: res.oauth_token_secret,
      userId: res.user_id,
      screenName: res.screen_name
    })
  )
  .catch(console.error);

And this will return you your access_token and access_token_secret.

Tweeting a thread

const client = new Twitter({
  consumer_key: "xyz",
  consumer_secret: "xyz",
  access_token_key: "xyz",
  access_token_secret: "xyz"
});

async function tweetThread(thread) {
  let lastTweetID = "";
  for (const status of thread) {
    const tweet = await client.post("statuses/update", {
      status: status,
      in_reply_to_status_id: lastTweetID,
      auto_populate_reply_metadata: true
    });
    lastTweetID = tweet.id_str;
  }
}

const thread = ["First tweet", "Second tweet", "Third tweet"];
tweetThread(thread).catch(console.error);

Streams

To learn more about the streaming API visit the Twitter Docs. The streaming API works only in Node.

const client = new Twitter({
  consumer_key: "xyz" // from Twitter.
  consumer_secret: "xyz" // from Twitter.
  access_token_key: "abc" // from your User (oauth_token)
  access_token_secret: "abc" // from your User (oauth_token_secret)
});

const parameters = {
  track: "#bitcoin,#litecoin,#monero",
  follow: "422297024,873788249839370240",  // @OrchardAI, @tylerbuchea
  locations: "-122.75,36.8,-121.75,37.8",  // Bounding box -	San Francisco
};

const stream = client.stream("statuses/filter", parameters)
  .on("start", response => console.log("start"))
  .on("data", tweet => console.log("data", tweet.text))
  .on("ping", () => console.log("ping"))
  .on("error", error => console.log("error", error))
  .on("end", response => console.log("end"));

// To stop the stream:
process.nextTick(() => stream.destroy());  // emits "end" and "error" events

To stop a stream, call stream.destroy(). That might take a while though, if the stream receives a lot of traffic. Also, if you attempt to destroy a stream from an on handler, you may get an error about writing to a destroyed stream. In that case, try to defer the destroy() call:

process.nextTick(() => stream.destroy());

After calling stream.destroy(), you can recreate the stream, if you wait long enough - see the "should reuse stream N times" test. Note that Twitter may return a "420 Enhance your calm" error if you switch streams too fast. There are no response headers specifying how long to wait, and the error, as well as streaming limits in general, are poorly documented. Trial and error has shown that for tracked keywords, waiting 20 to 30 seconds between re-creating streams was enough. Remember to also set up the .on() handlers again for the new stream.

Support for Twitter API v2

The new Twitter API v2 no longer requires the .json extension on its endpoints. In order to use v2, set version: '2' and extension: false.

const client = new Twitter({
  version: "2", // version "1.1" is the default (change for v2)
  extension: false, // true is the default (this must be set to false for v2 endpoints)
  consumer_key: "abc", // from Twitter.
  consumer_secret: "def", // from Twitter.
  access_token_key: "uvw", // from your User (oauth_token)
  access_token_secret: "xyz" // from your User (oauth_token_secret)
});

Methods

.get(endpoint, parameters)

Returns a Promise resolving to the API response object, or rejecting on error. The response and error objects also contain the HTTP response code and headers, under the _headers key. These are useful to check for rate limit information.

const client = new Twitter({
  consumer_key: "xyz",
  consumer_secret: "xyz",
  access_token_key: "abc",
  access_token_secret: "abc"
});

const rateLimits = await client.get("statuses/show", {
  id: "1016078154497048576"
});

.post(endpoint, parameters)

Same return as .get().

Use the .post method for actions that change state, or when the total size of the parameters might be too long for a GET request. For example, to follow a user:

const client = new Twitter({
  consumer_key: "xyz",
  consumer_secret: "xyz",
  access_token_key: "abc",
  access_token_secret: "abc"
});

await client.post("friendships/create", {
  screen_name: "dandv"
});

The second use case for POST is when you need to pass more parameters than suitable for the length of a URL, such as when looking up a larger number of user ids or screen names:

const users = await client.post("users/lookup", {
  screen_name: "longScreenName1,longerScreeName2,...,veryLongScreenName100"
});

.put(endpoint, query_parameters, request_body)

Same return as .get() and .post().

Use the .put method for actions that update state. For example, to update a welcome message.

const client = new Twitter({
  consumer_key: "xyz",
  consumer_secret: "xyz",
  access_token_key: "abc",
  access_token_secret: "abc"
});

const welcomeMessageID = "abc";

await client.put(
  "direct_messages/welcome_messages/update",
  {
    id: welcomeMessageID
  },
  {
    message_data: {
      text: "Welcome!!!"
    }
  }
);

.getBearerToken()

See the app authentication example.

.getRequestToken(twitterCallbackUrl)

See the OAuth example.

.getAccessToken(options)

See the OAuth example.

Examples

You can find many more examples for various resources/endpoints in the tests.

Troubleshooting

Headers on success

const tweets = await client.get("statuses/home_timeline");
console.log(`Rate: ${tweets._headers.get('x-rate-limit-remaining')} / ${tweets._headers.get('x-rate-limit-limit')}`);
const delta = (tweets._headers.get('x-rate-limit-reset') * 1000) - Date.now()
console.log(`Reset: ${Math.ceil(delta / 1000 / 60)} minutes`);

API errors

.get and .post reject on error, so you can use try/catch to handle errors. The error object contains an errors property with the error code and message, and a _headers property with the the HTTP response code and Headers object returned by the Twitter API.

try {
  const response = await client.get("some/endpoint");
  // ... use response here ...
} catch (e) {
  if ('errors' in e) {
    // Twitter API error
    if (e.errors[0].code === 88)
      // rate limit exceeded
      console.log("Rate limit will reset on", new Date(e._headers.get("x-rate-limit-reset") * 1000));
    else
      // some other kind of error, e.g. read-only API trying to POST
  } else {
    // non-API error, e.g. network problem or invalid JSON in response
  }
}

Rate limiting

A particular case of errors is exceeding the rate limits. See the example immediately above for detecting rate limit errors, and read Twitter's documentation on rate limiting.

Numeric vs. string IDs

Twitter uses numeric IDs that in practice can be up to 18 characters long. Due to rounding errors, it's unsafe to use numeric IDs in JavaScript. Always set stringify_ids: true when possible, so that Twitter will return strings instead of numbers, and rely on the id_str field, rather than on the id field.

Contributing

With the library nearing v1.0, contributions are welcome! Areas especially in need of help involve multimedia (see #33 for example), and adding tests (see these for reference).

Development

  1. Fork/clone the repo
  2. yarn/npm install
  3. Go to https://apps.twitter.com and create an app for testing this module. Make sure it has read/write permissions.
  4. Grab the consumer key/secret, and the access token/secret and place them in a .env file in the project's root directory, under the following variables:
    TWITTER_CONSUMER_KEY=...
    TWITTER_CONSUMER_SECRET=...
    ACCESS_TOKEN=...
    ACCESS_TOKEN_SECRET=...
    
  5. yarn/npm test and make sure all tests pass
  6. Add your contribution, along with test case(s). Note: feel free to skip the "should DM user" test during development by changing that it() call to it.skip(), but remember to revert that change before committing. This will prevent your account from being flagged as abusing the API to send too many DMs.
  7. Make sure all tests pass. NOTE: tests will take over 10 minutes to finish.
  8. Commit using a descriptive message (please squash commits into one per fix/improvement!)
  9. git push and submit your PR!

Credits

Authors:

Over the years, thanks to:

More Repositories

1

avatar-generator

Personas, an avatar generator by Draftbit
ReScript
1,263
star
2

reason-expo

ReasonML bindings for Expo
Reason
143
star
3

expo-walletconnect-demo

Connect a native iOS/Android app using Expo to a MetaMask wallet using WalletConnect without ejecting
TypeScript
117
star
4

react-native-jigsaw

Jigsaw, Draftbit's built-in component library
TypeScript
100
star
5

rescript-storybook

Storybook bindings for Rescript
ReScript
80
star
6

exp-deploy-cli

🖥 🗜 Deploy Expo apps to different environments (staging, production)
JavaScript
38
star
7

apollo-server-starter

💻 Apollo Server starter kit, utilizing HapiJS and KnexJS on top of PostgreSQL
JavaScript
22
star
8

re-font-awesome

ReasonML bindings to Font Awesome Pro
CSS
17
star
9

react-native-vertical-alphabet

Vertical Alphabet (Part 1 of 2)
JavaScript
16
star
10

bs-js-collections

Bindings to JavaScript primitive Set and Map types
ReScript
13
star
11

re-jest

Easier bindings to Jest for ReasonML
Reason
13
star
12

graphql-batching-helpers

📊 GraphQL Batching Helpers for Creating & Deleting
JavaScript
10
star
13

expo-crypto-polyfills

Polyfills required to build crypto apps in Expo without ejecting
JavaScript
8
star
14

bs-react-tabs

Reason bindings for react-tabs
Reason
5
star
15

hapi-airtable-authentication-plugin

A plugin that adds JWT-based authentication to a Hapi server, utlizing Airtable as a data store
JavaScript
5
star
16

application-asset-generator

🖼 A tool to quickly generate mobile app assets.
JavaScript
4
star
17

react-nav-current-route-name

🛣 🚴‍♀️ 🚧 Get the Current Route Name using React Navigation, Redux & Reselect
JavaScript
4
star
18

react-native-upload-photo-button

A simple upload / edit photo button with the camera and circle and everything
JavaScript
3
star
19

bs-analytics

Bucklescript Bindings for window.analytics (segment)
OCaml
3
star
20

awesome-uri-intents

JavaScript
2
star
21

variable-json

JSON syntax with variable interpolation
ReScript
2
star
22

draftbit-snack

JavaScript
2
star
23

hapi-airtable-sms-authentication-plugin

A plugin that adds Twilio SMS authentication to a Hapi server, utlizing Airtable as a data store
JavaScript
1
star