fix: improve unbookmark url handling
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
- `lists` + `list-timeline` commands for Twitter Lists (#21) — thanks @harperreed
|
- `lists` + `list-timeline` commands for Twitter Lists (#21) — thanks @harperreed
|
||||||
- Tweet JSON output now includes media items (photos, videos, GIFs) (#14) — thanks @Hormold
|
- Tweet JSON output now includes media items (photos, videos, GIFs) (#14) — thanks @Hormold
|
||||||
- Bookmarks can resume pagination from a cursor (#26) — thanks @leonho
|
- Bookmarks can resume pagination from a cursor (#26) — thanks @leonho
|
||||||
|
- `unbookmark` command to remove bookmarked tweets (#22) — thanks @mbelinky.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Feature flags can be overridden at runtime via `features.json` (refreshable via `query-ids`).
|
- Feature flags can be overridden at runtime via `features.json` (refreshable via `query-ids`).
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Extract tweet ID from a Twitter/X URL or return the input unchanged if it's already an ID.
|
* 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 {
|
export function extractTweetId(input: string): string {
|
||||||
// If it's a URL, extract the tweet ID
|
// If it's a URL, extract the tweet ID
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ export function withBookmarks<TBase extends AbstractConstructor<TwitterClientBas
|
|||||||
}
|
}
|
||||||
|
|
||||||
async unbookmark(tweetId: string): Promise<BookmarkMutationResult> {
|
async unbookmark(tweetId: string): Promise<BookmarkMutationResult> {
|
||||||
|
// 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 };
|
const variables = { tweet_id: tweetId };
|
||||||
let queryId = await this.getQueryId('DeleteBookmark');
|
let queryId = await this.getQueryId('DeleteBookmark');
|
||||||
let urlWithOperation = `${TWITTER_API_BASE}/${queryId}/DeleteBookmark`;
|
let urlWithOperation = `${TWITTER_API_BASE}/${queryId}/DeleteBookmark`;
|
||||||
|
|
||||||
const buildBody = () => JSON.stringify({ variables, queryId });
|
const buildBody = () => JSON.stringify({ variables, queryId });
|
||||||
|
const buildHeaders = () => ({ ...this.getHeaders(), referer: `https://x.com/i/status/${tweetId}` });
|
||||||
let body = buildBody();
|
let body = buildBody();
|
||||||
|
|
||||||
const headers = { ...this.getHeaders(), referer: `https://x.com/i/status/${tweetId}` };
|
|
||||||
|
|
||||||
const parseResponse = async (response: Response): Promise<BookmarkMutationResult> => {
|
const parseResponse = async (response: Response): Promise<BookmarkMutationResult> => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
@@ -42,7 +42,7 @@ export function withBookmarks<TBase extends AbstractConstructor<TwitterClientBas
|
|||||||
try {
|
try {
|
||||||
let response = await this.fetchWithTimeout(urlWithOperation, {
|
let response = await this.fetchWithTimeout(urlWithOperation, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers: buildHeaders(),
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,14 +54,14 @@ export function withBookmarks<TBase extends AbstractConstructor<TwitterClientBas
|
|||||||
|
|
||||||
response = await this.fetchWithTimeout(urlWithOperation, {
|
response = await this.fetchWithTimeout(urlWithOperation, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers: buildHeaders(),
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
const retry = await this.fetchWithTimeout(TWITTER_GRAPHQL_POST_URL, {
|
const retry = await this.fetchWithTimeout(TWITTER_GRAPHQL_POST_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers: buildHeaders(),
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ describe('CLI utilities', () => {
|
|||||||
expect(extractTweetId(url)).toBe('1234567890123456789');
|
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', () => {
|
it('should return ID as-is if already an ID', () => {
|
||||||
const id = '1234567890123456789';
|
const id = '1234567890123456789';
|
||||||
expect(extractTweetId(id)).toBe('1234567890123456789');
|
expect(extractTweetId(id)).toBe('1234567890123456789');
|
||||||
|
|||||||
Reference in New Issue
Block a user