diff --git a/lib/config.ts b/lib/config.ts index 7403d58f4..b97c7bec7 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -206,6 +206,8 @@ type ConfigEnvKeys = | 'TUMBLR_REFRESH_TOKEN' | 'TWITTER_CONSUMER_KEY' | 'TWITTER_CONSUMER_SECRET' + | 'TWITTER_ACCESS_TOKEN' + | 'TWITTER_ACCESS_SECRET' // | 'TWITTER_USERNAME' // | 'TWITTER_PASSWORD' // | 'TWITTER_AUTHENTICATION_SECRET' @@ -621,6 +623,8 @@ export type Config = { twitter: { consumerKey?: string; consumerSecret?: string; + accessToken?: string; + accessSecret?: string; // username?: string[]; // password?: string[]; // authenticationSecret?: string[]; @@ -1108,6 +1112,8 @@ const calculateValue = () => { twitter: { consumerKey: envs.TWITTER_CONSUMER_KEY, consumerSecret: envs.TWITTER_CONSUMER_SECRET, + accessToken: envs.TWITTER_ACCESS_TOKEN, + accessSecret: envs.TWITTER_ACCESS_SECRET, // username: envs.TWITTER_USERNAME?.split(','), // password: envs.TWITTER_PASSWORD?.split(','), // authenticationSecret: envs.TWITTER_AUTHENTICATION_SECRET?.split(','), diff --git a/lib/routes/twitter/api/developer-api/api.ts b/lib/routes/twitter/api/developer-api/api.ts index 1f2fcff79..e7de3f2ae 100644 --- a/lib/routes/twitter/api/developer-api/api.ts +++ b/lib/routes/twitter/api/developer-api/api.ts @@ -6,7 +6,12 @@ import ConfigNotFoundError from '@/errors/types/config-not-found'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import cache from '@/utils/cache'; -const appClients: TwitterApiReadOnly[] = []; +interface ClientWrapper { + client: TwitterApiReadOnly; + isUserAuth: boolean; +} + +const appClients: ClientWrapper[] = []; let index = -1; const init = () => { @@ -19,18 +24,37 @@ const init = () => { const consumerKeys = config.twitter.consumerKey.split(','); const consumerSecrets = config.twitter.consumerSecret.split(','); + const accessTokens = config.twitter.accessToken?.split(',') || []; + const accessSecrets = config.twitter.accessSecret?.split(',') || []; for (const [index, consumerKey] of consumerKeys.entries()) { const consumerSecret = consumerSecrets[index]; + const accessToken = accessTokens[index]; + const accessSecret = accessSecrets[index]; + if (!consumerKey || !consumerSecret) { continue; } - appClients.push( - new TwitterApi({ - appKey: consumerKey, - appSecret: consumerSecret, - }).readOnly - ); + + if (accessToken && accessSecret) { + appClients.push({ + client: new TwitterApi({ + appKey: consumerKey, + appSecret: consumerSecret, + accessToken, + accessSecret, + }).readOnly, + isUserAuth: true, + }); + } else { + appClients.push({ + client: new TwitterApi({ + appKey: consumerKey, + appSecret: consumerSecret, + }).readOnly, + isUserAuth: false, + }); + } } }; @@ -40,7 +64,10 @@ export const getAppClient = async () => { throw new ConfigNotFoundError('Twitter API is not configured'); } index += 1; - return await appClients[index % appClients.length].appLogin(); + + const currentWrapper = appClients[index % appClients.length]; + + return currentWrapper.isUserAuth ? currentWrapper.client : await currentWrapper.client.appLogin(); }; const mapUserToLegacy = (user: Record) => diff --git a/lib/routes/twitter/namespace.ts b/lib/routes/twitter/namespace.ts index acdba9a8d..f6ab61280 100644 --- a/lib/routes/twitter/namespace.ts +++ b/lib/routes/twitter/namespace.ts @@ -48,6 +48,7 @@ Currently supports two authentication methods: ~~- Using \`TWITTER_USERNAME\` \`TWITTER_PASSWORD\` and \`TWITTER_AUTHENTICATION_SECRET\`: Configure a comma-separated list of Twitter username and password. RSSHub will use this information to log in to Twitter and obtain data using the mobile API. Please note that if you have not logged in with the current IP address before, it is easy to trigger Twitter's risk control mechanism.~~ This no longer works since mobile client attestation has been implemented in October 2025. - Using \`TWITTER_CONSUMER_KEY\` and \`TWITTER_CONSUMER_SECRET\`: Configure a comma-separated list of Twitter API keys and secrets. RSSHub will use this information to access Twitter's Pay-Per-Use developer API to obtain data. +- OPTIONAL: Using \`TWITTER_ACCESS_TOKEN\` and \`TWITTER_ACCESS_SECRET\`: Configure a comma-separated list of Twitter API access tokens and secrets. RSSHub will use this information to access Twitter's Pay-Per-Use developer API with user authentication to obtain data. If not provided, RSSHub will only use app authentication, which may only access to public information. `, lang: 'en',