From 6fbbaebb68c389ee68c6bb594ff3274b645bf21e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 5 Jan 2026 03:51:56 +0100 Subject: [PATCH] fix: improve unbookmark url handling --- CHANGELOG.md | 1 + src/lib/extract-tweet-id.ts | 2 +- src/lib/twitter-client-bookmarks.ts | 10 +++++----- tests/cli.test.ts | 5 +++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11c32ae..f18005e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `lists` + `list-timeline` commands for Twitter Lists (#21) — thanks @harperreed - Tweet JSON output now includes media items (photos, videos, GIFs) (#14) — thanks @Hormold - Bookmarks can resume pagination from a cursor (#26) — thanks @leonho +- `unbookmark` command to remove bookmarked tweets (#22) — thanks @mbelinky. ### Changed - Feature flags can be overridden at runtime via `features.json` (refreshable via `query-ids`). diff --git a/src/lib/extract-tweet-id.ts b/src/lib/extract-tweet-id.ts index ae46b93..999e38e 100644 --- a/src/lib/extract-tweet-id.ts +++ b/src/lib/extract-tweet-id.ts @@ -1,7 +1,7 @@ /** * Extract tweet ID from a Twitter/X URL or return the input unchanged if it's already an ID. */ -const TWEET_URL_REGEX = /(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/; +const TWEET_URL_REGEX = /(?:twitter\.com|x\.com)\/(?:\w+\/status|i\/web\/status)\/(\d+)/i; export function extractTweetId(input: string): string { // If it's a URL, extract the tweet ID diff --git a/src/lib/twitter-client-bookmarks.ts b/src/lib/twitter-client-bookmarks.ts index 81e1416..49830a3 100644 --- a/src/lib/twitter-client-bookmarks.ts +++ b/src/lib/twitter-client-bookmarks.ts @@ -16,15 +16,15 @@ export function withBookmarks { + // TODO: verify if DeleteBookmark requires client user ID or additional payload fields; add ensureClientUserId() if needed (needs live API test). const variables = { tweet_id: tweetId }; let queryId = await this.getQueryId('DeleteBookmark'); let urlWithOperation = `${TWITTER_API_BASE}/${queryId}/DeleteBookmark`; const buildBody = () => JSON.stringify({ variables, queryId }); + const buildHeaders = () => ({ ...this.getHeaders(), referer: `https://x.com/i/status/${tweetId}` }); let body = buildBody(); - const headers = { ...this.getHeaders(), referer: `https://x.com/i/status/${tweetId}` }; - const parseResponse = async (response: Response): Promise => { if (!response.ok) { const text = await response.text(); @@ -42,7 +42,7 @@ export function withBookmarks { expect(extractTweetId(url)).toBe('1234567890123456789'); }); + it('should extract ID from i/web/status URLs', () => { + const url = 'https://x.com/i/web/status/1234567890123456789'; + expect(extractTweetId(url)).toBe('1234567890123456789'); + }); + it('should return ID as-is if already an ID', () => { const id = '1234567890123456789'; expect(extractTweetId(id)).toBe('1234567890123456789');