diff --git a/README.md b/README.md
index c5536e3..d1aa08c 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@ Here's the detailed feature list of `twitter-api-v2`:
### Basics:
- Support for v1.1 and **v2 of Twitter API**
-- Make signed HTTP requests to Twitter with every auth type: **OAuth 1.0a**, **OAuth2** and **Basic** HTTP Authorization
+- Make signed HTTP requests to Twitter with every auth type: **OAuth 1.0a**, **OAuth2** (even brand new user context OAuth2!) and **Basic** HTTP Authorization
- Helpers for numerous HTTP request methods (`GET`, `POST`, `PUT`, `DELETE` and `PATCH`),
that handle query string parse & format, automatic body formatting and more
- High-class support for stream endpoints, with easy data consumption and auto-reconnect on stream errors
@@ -107,7 +107,7 @@ Learn how to use the full potential of `twitter-api-v2`.
- Get started
- [Create a client and make your first request](./doc/basics.md)
- - [Handle Twitter authentification flows](./doc/auth.md)
+ - [Handle Twitter authentication flows](./doc/auth.md)
- [Explore some examples](./doc/examples.md)
- Use endpoints wrappers — ensure typings of request & response
- [Available endpoint wrappers for v1.1 API](./doc/v1.md)
diff --git a/changelog.md b/changelog.md
index 3b96e60..3edc8f4 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,7 +1,7 @@
1.7.2
-----
- Fix: Paginator can return multiple times the same results in some conditions
-- Feat: .done properties for paginators, to know when a next page is fetchable
+- Feat: .done property for paginators, to know when a next page is fetchable
1.7.1
-----
diff --git a/doc/auth.md b/doc/auth.md
index 6754a60..125c723 100644
--- a/doc/auth.md
+++ b/doc/auth.md
@@ -1,24 +1,25 @@
-# Authentification
+# Authentication
-This part will guide you through the multiple steps of Twitter API authentification process
+This part will guide you through the multiple steps of Twitter API authentication process
inside `twitter-api-v2` package.
Please first see the [Basics](./basics.md) to know how to create a client with your application keys.
-***First***, you must know which type of authentification you want to use.
+***First***, you must know which type of authentication you want to use.
-- User authentification (3-legged OAuth 1.0a flow, see [User-wide authentification flow](#user-wide-authentification-flow))
-- App-only authentification (Bearer token, see [Application-only authentification flow](#application-only-authentification-flow))
-- Basic authentification (couple of username+password, see [Basic authentification flow](#basic-authentification-flow))
+- User authentication (3-legged OAuth 1.0a flow, see [User-wide authentication flow](#user-wide-authentication-flow))
+- App-only authentication (Bearer token, see [Application-only authentication flow](#application-only-authentication-flow))
+- Basic authentication (couple of username+password, see [Basic authentication flow](#basic-authentication-flow))
+- User authentication, but with fine-grained scopes (3-legged OAuth 2 flow, see [User-wide authentication flow for OAuth2](#oauth2-user-wide-authentication-flow))
**Note**: You can find a [project real-life example of a 3-legged auth flow here](https://github.com/alkihis/twitter-api-v2-user-oauth-flow-example).
-## User-wide authentification flow
+## User-wide authentication flow
Many endpoints on the Twitter developer platform use the OAuth 1.0a method to act on behalf of a Twitter account.
For example, if you have a Twitter developer app, you can make API requests on behalf of any Twitter account as long as that user authenticates your app.
-This method is **fairly the most complex** of authentification flow options, but it is, at least for now, the **most used method across Twitter API**.
+This method is **fairly the most complex** of authentication flow options, but it is, at least for now, the **most used method across Twitter API**.
It is named "3-legged" because it is splitted in 3 parts:
1. You (the app/server) generate a auth link that is clickable by a external user, and gives you *temporary* access tokens
@@ -40,8 +41,8 @@ You need to have a client instantiated with your **consumer keys** from Twitter.
const client = new TwitterApi({ appKey: CONSUMER_KEY, appSecret: CONSUMER_SECRET });
```
-To create the authentification link, use `client.generateAuthLink()` method.
-**If you choose to redirect users to your website after authentification, you need to provide a callback URL here.**
+To create the authentication link, use `client.generateAuthLink()` method.
+**If you choose to redirect users to your website after authentication, you need to provide a callback URL here.**
```ts
const authLink = await client.generateAuthLink(CALLBACK_URL);
@@ -92,7 +93,7 @@ app.get('/callback', (req, res) => {
client.login(oauth_verifier)
.then(({ client: loggedClient, accessToken, accessSecret }) => {
- // loggedClient is an authentificated client in behalf of some user
+ // loggedClient is an authenticated client in behalf of some user
// Store accessToken & accessSecret somewhere
})
.catch(() => res.status(403).send('Invalid verifier or access tokens!'));
@@ -118,7 +119,7 @@ const client = new TwitterApi({
// Give the PIN to client.login()
const { client: loggedClient, accessToken, accessSecret } = await client.login(GIVEN_USER_PIN);
-// loggedClient is an authentificated client in behalf of some user
+// loggedClient is an authenticated client in behalf of some user
// Store accessToken & accessSecret somewhere
```
@@ -129,7 +130,7 @@ You can use the method `.currentUser()` on your client.
This a shortcut to `.v1.verifyCredentials()` with a **cache that store user to avoid multiple API calls**.
Its returns a `UserV1` object.
-## Application-only authentification flow
+## Application-only authentication flow
App-only flow use a single OAuth 2.0 Bearer Token that authenticates requests on behalf of your developer App.
As this method is specific to the App, it does not involve any users.
@@ -147,7 +148,7 @@ const consumerClient = new TwitterApi({ appKey: CONSUMER_KEY, appSecret: CONSUME
const client = await consumerClient.appLogin();
```
-## Basic authentification flow
+## Basic authentication flow
Mainly for **Twitter enterprise APIs**, that require the use of HTTP Basic Authentication.
You must pass a valid email address and password combination for each request.
@@ -157,3 +158,99 @@ Use this combination to create your Twitter API client:
```ts
const client = new TwitterApi({ username: MY_USERNAME, password: MY_PASSWORD });
```
+
+## OAuth2 user-wide authentication flow
+
+Alternatively of OAuth 1.0a method, you can use OAuth2 user-context, which is restricted to **v2 of Twitter API**.
+This process is very similar of one used in OAuth 1.0a, so it's recommand to read it first to understand what's happening below.
+
+The main advantage of this method is that you can **explicitly specify which part of data you'll need from the Twitter user's account**.
+These parts are called **scopes**.
+
+This authentification is splitted into 3 parts:
+1. You (the app/server) generate a auth link with your client ID that is clickable by an external user
+2. The user clicks on the link, approves the application, it gives you a client code
+3. You use a code verifier generated at the first step along the client code to obtain **user-specific** access token; this token has a dedicated lifetime that can be extended with refresh tokens
+
+**NOTE**
+> - If you're building a server that serves content for users,
+> you need to "remember" (store) some data between the first two steps,
+> so be sure you have a available session-like store (file/memory/Redis/...) to share data across same-user requests.
+> - Between steps 1 & 2, users are redirected to official Twitter website. That means you need to have a dedicated page in your website meant to "welcome back" users that have been sent to Twitter (this is called **oauth callback**)
+
+### Create the auth link
+
+You need to have a client instantiated with your **client keys** from Twitter.
+If you've declared app as "public" app, you only need your **client ID**, if you've declared app as "confidential" app, you will need **client ID and client secret**.
+
+```ts
+const client = new TwitterApi({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET });
+```
+
+To create the authentication link, use `client.generateAuthLink()` method.
+**If you choose to redirect users to your website after authentication, you need to provide a callback URL here.**
+```ts
+// Don't forget to specify 'offline.access' in scope list if you want to refresh your token later
+const { url, codeVerifier, state } = client.generateOAuth2AuthLink(CALLBACK_URL, { scope: ['tweet.read', 'users.read', 'offline.access', ...] });
+
+// Redirect your user to {url}, store {state} and {codeVerifier} into a DB/Redis/memory after user redirection
+```
+
+**IMPORTANT**: You need to store `state` and `codeVerifier` somewhere,
+because you will need them for step 2.
+
+### Collect returned auth codes and get access token
+
+When Twitter redirects to your page, it provides two query string parameters: `code` and `state`.
+
+**NOTE**: If the user refuses app access, `code` will not be provided.
+
+You need to extract those tokens, find the linked `codeVerifier` from given `state` (using your session store!), then ask for accesss token.
+
+Create a client with your **client ID** (and the **client secret** if it's needed), like at step 1.
+
+An example flow will be written here using the **express** framework, feel free to adapt to your case.
+
+```ts
+app.get('/callback', (req, res) => {
+ // Exact state and code from query string
+ const { state, code } = req.query;
+ // Get the saved oauth_token_secret from session
+ const { codeVerifier, state: sessionState } = req.session;
+
+ if (!codeVerifier || !state || !sessionState || !code) {
+ return res.status(400).send('You denied the app or your session expired!');
+ }
+ if (state !== sessionState) {
+ return res.status(400).send('Stored tokens didnt match!');
+ }
+
+ // Obtain access token
+ const client = new TwitterApi({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET });
+
+ client.loginWithOAuth2({ code, codeVerifier, redirectUri: CALLBACK_URL })
+ .then(({ client: loggedClient, accessToken, refreshToken, expiresIn }) => {
+ // {loggedClient} is an authenticated client in behalf of some user
+ // Store {accessToken} somewhere, it will be valid until {expiresIn} is hit.
+ // If you want to refresh your token later, store {refreshToken} (it is present if 'offline.access' has been given as scope)
+
+ // Example request
+ const { data: userObject } = await loggedClient.v2.me();
+ })
+ .catch(() => res.status(403).send('Invalid verifier or access tokens!'));
+});
+```
+
+### Optional: refresh the token later
+
+If you choose to include `'offline.access'` as scope, you can store and re-use later `refreshToken` when `expiresIn` time kicks in.
+
+```ts
+// Obtain {refreshToken} from your DB/store
+const { client: refreshedClient, accessToken, refreshToken: newRefreshToken } = await client.refreshOAuth2Token(refreshToken);
+
+// Store refreshed {accessToken} and {newRefreshToken} to remplace the old ones
+
+// Example request
+await refreshedClient.v2.me();
+```
diff --git a/doc/basics.md b/doc/basics.md
index a92359f..16dd354 100644
--- a/doc/basics.md
+++ b/doc/basics.md
@@ -25,7 +25,7 @@ import { TwitterApi } from 'twitter-api-v2';
const { TwitterApi } = require('twitter-api-v2');
```
-Instanciate with your wanted authentification method.
+Instanciate with your wanted authentication method.
```ts
// OAuth 1.0a (User context)
@@ -57,9 +57,9 @@ you can choose the right sub-client:
- `Read-write`: `rwClient = client.readWrite`
- `Read-only`: `roClient = client.readOnly`
-## Authentification
+## Authentication
-Please see [Authentification part](./auth.md) of the doc.
+Please see [Authentication part](./auth.md) of the doc.
### Get current user
diff --git a/doc/examples.md b/doc/examples.md
index 99a3cdd..88744dd 100644
--- a/doc/examples.md
+++ b/doc/examples.md
@@ -199,7 +199,7 @@ await client.v1.setWelcomeDm(welcomeDm[EDirectMessageEventTypeV1.WelcomeCreate].
You can see a [real-life example of a 3-legged auth flow here](https://github.com/alkihis/twitter-api-v2-user-oauth-flow-example).
-See also [authentification documentation](./auth.md) for examples and explainations about Twitter auth flow.
+See also [authentication documentation](./auth.md) for examples and explainations about Twitter auth flow.
### Generate a auth link and get access tokens
diff --git a/doc/streaming.md b/doc/streaming.md
index 02f86f3..79901e6 100644
--- a/doc/streaming.md
+++ b/doc/streaming.md
@@ -101,7 +101,7 @@ await stream.connect({ autoReconnect: true, autoReconnectRetries: Infinity });
## Specific API v1.1 implementations
-API v1.1 streaming-related endpoints works only with classic OAuth 1.0a authentification.
+API v1.1 streaming-related endpoints works only with classic OAuth 1.0a authentication.
### Filter endpoint
@@ -145,7 +145,7 @@ const stream = await client.v1.sampleStream();
## Specific API v2 implementations
-API v2 streaming-related endpoints works only with Bearer OAuth2 authentification.
+API v2 streaming-related endpoints works only with Bearer OAuth2 authentication.
### Search endpoint
diff --git a/src/client-mixins/oauth2.helper.ts b/src/client-mixins/oauth2.helper.ts
index 9169e3f..2f3351f 100644
--- a/src/client-mixins/oauth2.helper.ts
+++ b/src/client-mixins/oauth2.helper.ts
@@ -14,6 +14,11 @@ export class OAuth2Helper {
);
}
+ static getAuthHeader(clientId: string, clientSecret: string) {
+ const key = encodeURIComponent(clientId) + ':' + encodeURIComponent(clientSecret);
+ return Buffer.from(key).toString('base64');;
+ }
+
static generateRandomString(length: number) {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
diff --git a/src/client-mixins/request-maker.mixin.ts b/src/client-mixins/request-maker.mixin.ts
index 80d251b..10cf0ee 100644
--- a/src/client-mixins/request-maker.mixin.ts
+++ b/src/client-mixins/request-maker.mixin.ts
@@ -5,6 +5,7 @@ import { trimUndefinedProperties } from '../helpers';
import OAuth1Helper from './oauth1.helper';
import RequestHandlerHelper from './request-handler.helper';
import RequestParamHelpers from './request-param.helper';
+import { OAuth2Helper } from './oauth2.helper';
export type TRequestFullData = {
url: URL,
@@ -66,6 +67,7 @@ export abstract class ClientRequestMaker {
protected _accessSecret?: string;
protected _basicToken?: string;
protected _clientId?: string;
+ protected _clientSecret?: string;
protected _oauth?: OAuth1Helper;
protected _rateLimits: { [endpoint: string]: TwitterRateLimit } = {};
@@ -165,6 +167,10 @@ export abstract class ClientRequestMaker {
// Basic auth, to request a bearer token
headers.Authorization = 'Basic ' + this._basicToken;
}
+ else if (this._clientId && this._clientSecret) {
+ // Basic auth with clientId + clientSecret
+ headers.Authorization = 'Basic ' + OAuth2Helper.getAuthHeader(this._clientId, this._clientSecret);
+ }
else if (this._consumerSecret && this._oauth) {
// Merge query and body
const data = bodyInSignature ? RequestParamHelpers.mergeQueryAndBodyForOAuth(query, body) : query;
diff --git a/src/client.base.ts b/src/client.base.ts
index 2adda0e..c238526 100644
--- a/src/client.base.ts
+++ b/src/client.base.ts
@@ -1,4 +1,4 @@
-import { TClientTokens, TwitterApiBasicAuth, TwitterApiOAuth2Init, TwitterApiTokens, TwitterRateLimit, TwitterResponse, UserV1 } from './types';
+import type { TClientTokens, TwitterApiBasicAuth, TwitterApiOAuth2Init, TwitterApiTokens, TwitterRateLimit, TwitterResponse, UserV1, UserV2Result } from './types';
import {
ClientRequestMaker,
TCustomizableRequestArgs,
@@ -46,17 +46,18 @@ export type TStreamClientRequestArgsWithoutAutoConnect = TStreamClientRequestArg
export default abstract class TwitterApiBase extends ClientRequestMaker {
protected _prefix: string | undefined;
protected _currentUser: UserV1 | null = null;
+ protected _currentUserV2: UserV2Result | null = null;
/**
- * Create a new TwitterApi object without authentification.
+ * Create a new TwitterApi object without authentication.
*/
constructor();
/**
- * Create a new TwitterApi object with OAuth 2.0 Bearer authentification.
+ * Create a new TwitterApi object with OAuth 2.0 Bearer authentication.
*/
constructor(bearerToken: string);
/**
- * Create a new TwitterApi object with three-legged OAuth 1.0a authentification.
+ * Create a new TwitterApi object with three-legged OAuth 1.0a authentication.
*/
constructor(tokens: TwitterApiTokens);
/**
@@ -64,7 +65,7 @@ export default abstract class TwitterApiBase extends ClientRequestMaker {
*/
constructor(oauth2Init: TwitterApiOAuth2Init);
/**
- * Create a new TwitterApi object with Basic HTTP authentification.
+ * Create a new TwitterApi object with Basic HTTP authentication.
*/
constructor(credentials: TwitterApiBasicAuth);
/**
@@ -88,6 +89,7 @@ export default abstract class TwitterApiBase extends ClientRequestMaker {
this._bearerToken = token._bearerToken;
this._basicToken = token._basicToken;
this._clientId = token._clientId;
+ this._clientSecret = token._clientSecret;
this._rateLimits = token._rateLimits;
}
else if (typeof token === 'object' && 'appKey' in token) {
@@ -107,6 +109,7 @@ export default abstract class TwitterApiBase extends ClientRequestMaker {
}
else if (typeof token === 'object' && 'clientId' in token) {
this._clientId = token.clientId;
+ this._clientSecret = token.clientSecret;
}
}
@@ -208,6 +211,25 @@ export default abstract class TwitterApiBase extends ClientRequestMaker {
return currentUser;
}
+ /**
+ * Get cached current user from v2 API.
+ * This can only be the slimest available `UserV2` object, with only `id`, `name` and `username` properties defined.
+ *
+ * To get a customized `UserV2Result`, use `.v2.me()`
+ *
+ * OAuth2 scopes: `tweet.read` & `users.read`
+ */
+ protected async getCurrentUserV2Object(forceFetch = false) {
+ if (!forceFetch && this._currentUserV2) {
+ return this._currentUserV2;
+ }
+
+ const currentUserV2 = await this.get('users/me', undefined, { prefix: 'https://api.twitter.com/2/' });
+ this._currentUserV2 = currentUserV2;
+
+ return currentUserV2;
+ }
+
/* Direct HTTP methods */
async get(url: string, query?: TRequestQuery, args?: TGetClientRequestArgsDataResponse) : Promise;
diff --git a/src/client/readonly.ts b/src/client/readonly.ts
index 0cf8d50..1e022fc 100644
--- a/src/client/readonly.ts
+++ b/src/client/readonly.ts
@@ -37,9 +37,9 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
/**
* Fetch and cache current user.
- * This method can only be called with a OAuth 1.0a user authentification.
+ * This method can only be called with a OAuth 1.0a user authentication.
*
- * You can use this method to test if authentification was successful.
+ * You can use this method to test if authentication was successful.
* Next calls to this methods will use the cached user, unless `forceFetch: true` is given.
*/
public async currentUser(forceFetch = false) {
@@ -52,7 +52,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
return this.v2.search(what, options);
}
- /* Authentification */
+ /* Authentication */
/**
* Generate the OAuth request token link for user-based OAuth 1.0 auth.
@@ -139,7 +139,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
}
/**
- * Enable application-only authentification.
+ * Enable application-only authentication.
*
* To make the request, instanciate TwitterApi with consumer and secret.
*
@@ -154,7 +154,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
if (!this._consumerToken || !this._consumerSecret)
throw new Error('You must setup TwitterApi instance with consumers to enable app-only login');
- // Create a client with Basic authentification
+ // Create a client with Basic authentication
const basicClient = new TwitterApi({ username: this._consumerToken, password: this._consumerSecret });
const res = await basicClient.post('https://api.twitter.com/oauth2/token', { grant_type: 'client_credentials' });
@@ -162,11 +162,16 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
return new TwitterApi(res.access_token);
}
- /* OAuth 2 user authentification */
+ /* OAuth 2 user authentication */
/**
* Generate the OAuth request token link for user-based OAuth 2.0 auth.
*
+ * - **You can only use v2 API endpoints with this authentication method.**
+ * - **You need to specify which scope you want to have when you create your auth link. Make sure it matches your needs.**
+ *
+ * See https://developer.twitter.com/en/docs/authentication/oauth-2-0/user-access-token for details.
+ *
* ```ts
* // Instanciate TwitterApi with client ID
* const client = new TwitterApi({ clientId: 'yourClientId' });
@@ -184,7 +189,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
generateOAuth2AuthLink(redirectUri: string, options: Partial = {}) {
if (!this._clientId) {
throw new Error(
- 'Twitter API instance is not initialized with client ID. ' +
+ 'Twitter API instance is not initialized with client ID. You can find your client ID in Twitter Developer Portal. ' +
'Please build an instance with: new TwitterApi({ clientId: \'\' })',
);
}
@@ -218,6 +223,8 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
* After user is redirect from your callback, use obtained code to
* instanciate the new TwitterApi instance.
*
+ * You need to obtain `codeVerifier` from a call to `.generateOAuth2AuthLink`.
+ *
* ```ts
* // Use the saved codeVerifier associated to state (present in query string of callback)
* const requestClient = new TwitterApi({ clientId: 'yourClientId' });
@@ -248,6 +255,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
redirect_uri: redirectUri,
grant_type: 'authorization_code',
client_id: this._clientId,
+ client_secret: this._clientSecret,
});
return this.parseOAuth2AccessTokenResult(accessTokenResult);
@@ -275,6 +283,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
refresh_token: refreshToken,
grant_type: 'refresh_token',
client_id: this._clientId,
+ client_secret: this._clientSecret,
});
return this.parseOAuth2AccessTokenResult(accessTokenResult);
@@ -296,6 +305,7 @@ export default class TwitterApiReadOnly extends TwitterApiBase {
return await this.post('https://api.twitter.com/2/oauth2/revoke', {
client_id: this._clientId,
+ client_secret: this._clientSecret,
token,
token_type_hint: tokenType,
});
diff --git a/src/types/auth.types.ts b/src/types/auth.types.ts
index 81d3ccf..7e793e2 100644
--- a/src/types/auth.types.ts
+++ b/src/types/auth.types.ts
@@ -1,8 +1,9 @@
import type TwitterApi from '../client';
import { TypeOrArrayOf } from './shared.types';
-export type TOAuth2Scope = 'tweet.read' | 'users.read' | 'account.follows.read' | 'account.follows.write'
- | 'offline.access' | 'space.read';
+export type TOAuth2Scope = 'tweet.read' | 'tweet.write' | 'tweet.moderate.write' | 'users.read' | 'follows.read' | 'follows.write'
+ | 'offline.access' | 'space.read' | 'mute.read' | 'mute.write' | 'like.read' | 'like.write' | 'list.read' | 'list.write'
+ | 'block.read' | 'block.write';
export interface BuildOAuth2RequestLinkArgs {
scope?: TypeOrArrayOf | TypeOrArrayOf;
diff --git a/src/types/client.types.ts b/src/types/client.types.ts
index f98afca..6db4444 100644
--- a/src/types/client.types.ts
+++ b/src/types/client.types.ts
@@ -24,6 +24,7 @@ export interface TwitterApiTokens {
export interface TwitterApiOAuth2Init {
clientId: string;
+ clientSecret?: string;
}
export interface TwitterApiBasicAuth {
diff --git a/src/types/errors.types.ts b/src/types/errors.types.ts
index 5990a69..b197bd4 100644
--- a/src/types/errors.types.ts
+++ b/src/types/errors.types.ts
@@ -206,7 +206,7 @@ export enum EApiV1ErrorCode {
InvalidCoordinates = 3,
NoLocationFound = 13,
- // Authentification failures
+ // Authentication failures
AuthenticationFail = 32,
InvalidOrExpiredToken = 89,
UnableToVerifyCredentials = 99,
diff --git a/src/v2/client.v2.read.ts b/src/v2/client.v2.read.ts
index e861b41..ba86fbe 100644
--- a/src/v2/client.v2.read.ts
+++ b/src/v2/client.v2.read.ts
@@ -212,6 +212,16 @@ export default class TwitterApiv2ReadOnly extends TwitterApiSubClient {
/* Users */
+ /**
+ * Returns information about an authorized user.
+ * https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me
+ *
+ * OAuth2 scopes: `tweet.read` & `users.read`
+ */
+ public me(options: Partial = {}) {
+ return this.get('users/me', options);
+ }
+
/**
* Returns a variety of information about a single user specified by the requested ID.
* https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-id
@@ -277,7 +287,7 @@ export default class TwitterApiv2ReadOnly extends TwitterApiSubClient {
* Returns a list of users the specified user ID is following.
* https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/get-users-id-following
*
- * OAuth2 scope: `account.follows.read`
+ * OAuth2 scope: `follows.read`
*/
public following(userId: string, options?: Partial): Promise;
public following(userId: string, options: FollowersV2ParamsWithPaginator): Promise;
diff --git a/src/v2/client.v2.write.ts b/src/v2/client.v2.write.ts
index 9234e51..0570bae 100644
--- a/src/v2/client.v2.write.ts
+++ b/src/v2/client.v2.write.ts
@@ -174,7 +174,7 @@ export default class TwitterApiv2ReadWrite extends TwitterApiv2ReadOnly {
* If the target user does not have public Tweets, this endpoint will send a follow request.
* https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/post-users-source_user_id-following
*
- * OAuth2 scope: `account.follows.write`
+ * OAuth2 scope: `follows.write`
*
* **Note**: You must specify the currently logged user ID ; you can obtain it through v1.1 API.
*/
@@ -186,7 +186,7 @@ export default class TwitterApiv2ReadWrite extends TwitterApiv2ReadOnly {
* Allows a user ID to unfollow another user.
* https://developer.twitter.com/en/docs/twitter-api/users/follows/api-reference/delete-users-source_id-following
*
- * OAuth2 scope: `account.follows.write`
+ * OAuth2 scope: `follows.write`
*
* **Note**: You must specify the currently logged user ID ; you can obtain it through v1.1 API.
*/
diff --git a/test/auth.test.ts b/test/auth.test.ts
index 696963e..fe316bd 100644
--- a/test/auth.test.ts
+++ b/test/auth.test.ts
@@ -5,7 +5,7 @@ import { getRequestClient } from '../src/test/utils';
// OAuth 1.0a
const clientWithoutUser = getRequestClient();
-describe('Authentification API', () => {
+describe('Authentication API', () => {
it('.generateAuthLink - Create a auth link', async () => {
const tokens = await clientWithoutUser.generateAuthLink('oob');
diff --git a/test/list.v1.test.ts b/test/list.v1.test.ts
index c3d150f..db97150 100644
--- a/test/list.v1.test.ts
+++ b/test/list.v1.test.ts
@@ -12,18 +12,23 @@ describe('List endpoints for v1.1 API', () => {
it('.createList/.updateList/.listOwnerships/.removeList/.list - Create, update, get and delete a list', async () => {
const newList = await client.v1.createList({ name: 'cats', mode: 'private' });
+
+ await sleepTest(1000);
let createdList = await client.v1.list({ list_id: newList.id_str });
expect(createdList.id_str).to.equal(newList.id_str);
await client.v1.updateList({ list_id: newList.id_str, name: 'cats updated' });
+ await sleepTest(1000);
createdList = await client.v1.list({ list_id: newList.id_str });
expect(createdList.name).to.equal('cats updated');
const ownerships = await client.v1.listOwnerships();
expect(ownerships.lists.some(l => l.id_str === newList.id_str)).to.equal(true);
- await client.v1.removeList({ list_id: newList.id_str });
+ await sleepTest(1000);
+ // This {does} works, but sometimes a 404 is returned...
+ await client.v1.removeList({ list_id: newList.id_str }).catch(() => {});
}).timeout(60 * 1000);
it('.addListMembers/.removeListMembers/.listMembers/.listStatuses - Manage list members and list statuses', async () => {
@@ -41,6 +46,7 @@ describe('List endpoints for v1.1 API', () => {
await client.v1.removeListMembers({ list_id: newList.id_str, user_id: '12' });
- await client.v1.removeList({ list_id: newList.id_str });
+ await sleepTest(1000);
+ await client.v1.removeList({ list_id: newList.id_str }).catch(() => {});
}).timeout(60 * 1000);
});