diff --git a/src/api.ts b/src/api.ts index 39536bbe..b800d2ff 100644 --- a/src/api.ts +++ b/src/api.ts @@ -81,12 +81,16 @@ export async function requestApi( auth instanceof TwitterGuestAuth && auth.options?.experimental?.xClientTransactionId ) { - const transactionId = await generateTransactionId( - url, - auth.fetch.bind(auth), - method, - ); - headers.set('x-client-transaction-id', transactionId); + try { + const transactionId = await generateTransactionId( + url, + auth.fetch.bind(auth), + method, + ); + headers.set('x-client-transaction-id', transactionId); + } catch (err) { + log('Failed to generate x-client-transaction-id, falling back gracefully: %s', (err as Error).message); + } } if (body && method === 'POST') { diff --git a/src/auth-user.ts b/src/auth-user.ts index 4ff3dacf..74f69a7b 100644 --- a/src/auth-user.ts +++ b/src/auth-user.ts @@ -969,12 +969,16 @@ export class TwitterUserAuth extends TwitterGuestAuth { // Generate x-client-transaction-id if enabled - real browsers send this during login. if (this.options?.experimental?.xClientTransactionId) { - const transactionId = await generateTransactionId( - onboardingTaskUrl, - this.fetch.bind(this), - 'POST', - ); - headers.set('x-client-transaction-id', transactionId); + try { + const transactionId = await generateTransactionId( + onboardingTaskUrl, + this.fetch.bind(this), + 'POST', + ); + headers.set('x-client-transaction-id', transactionId); + } catch (err) { + log('Failed to generate x-client-transaction-id, falling back gracefully: %s', (err as Error).message); + } } // Strip flow_name from the body: real browsers only send it in the URL query parameter. diff --git a/src/tweets.ts b/src/tweets.ts index c427d146..0d00fb94 100644 --- a/src/tweets.ts +++ b/src/tweets.ts @@ -377,10 +377,13 @@ export async function getLatestTweet( ): Promise { const timeline = getTweets(user, max, auth); - // No point looping if max is 1, just use first entry. - return max === 1 - ? (await timeline.next()).value - : await getTweetWhere(timeline, { isRetweet: includeRetweets }); + return getTweetWhere(timeline, (tweet) => { + // Skip pinned tweets; they always appear first in the timeline. + if (tweet.isPin) return false; + // If includeRetweets is false, skip retweets too. + if (!includeRetweets && tweet.isRetweet) return false; + return true; + }); } export interface TweetResultByRestId {