feat: add unbookmark command to remove bookmarks
This commit is contained in:
committed by
Peter Steinberger
parent
1d1ed6b515
commit
04a0c98362
@@ -2,6 +2,7 @@
|
||||
"CreateTweet": "Uf3io9zVp1DsYxrmL5FJ7g",
|
||||
"CreateRetweet": "LFho5rIi4xcKO90p9jwG7A",
|
||||
"FavoriteTweet": "lI07N6Otwv1PhnEgXILM7A",
|
||||
"DeleteBookmark": "Wlmlj2-xzyS1GN3a6cj-mQ",
|
||||
"TweetDetail": "97JF30KziU00483E_8elBA",
|
||||
"SearchTimeline": "M1jEez78PEfVfbQLvlWMvQ",
|
||||
"Bookmarks": "RV1g3b8n_SGOHwkqKYSCFw",
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { AbstractConstructor, Mixin, TwitterClientBase } from './twitter-client-base.js';
|
||||
import { TWITTER_API_BASE, TWITTER_GRAPHQL_POST_URL } from './twitter-client-constants.js';
|
||||
import type { BookmarkMutationResult } from './twitter-client-types.js';
|
||||
|
||||
export interface TwitterClientBookmarkMethods {
|
||||
unbookmark(tweetId: string): Promise<BookmarkMutationResult>;
|
||||
}
|
||||
|
||||
export function withBookmarks<TBase extends AbstractConstructor<TwitterClientBase>>(
|
||||
Base: TBase,
|
||||
): Mixin<TBase, TwitterClientBookmarkMethods> {
|
||||
abstract class TwitterClientBookmarks extends Base {
|
||||
// biome-ignore lint/complexity/noUselessConstructor lint/suspicious/noExplicitAny: TS mixin constructor requirement.
|
||||
constructor(...args: any[]) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
async unbookmark(tweetId: string): Promise<BookmarkMutationResult> {
|
||||
const variables = { tweet_id: tweetId };
|
||||
let queryId = await this.getQueryId('DeleteBookmark');
|
||||
let urlWithOperation = `${TWITTER_API_BASE}/${queryId}/DeleteBookmark`;
|
||||
|
||||
const buildBody = () => JSON.stringify({ variables, queryId });
|
||||
let body = buildBody();
|
||||
|
||||
const headers = { ...this.getHeaders(), referer: `https://x.com/i/status/${tweetId}` };
|
||||
|
||||
const parseResponse = async (response: Response): Promise<BookmarkMutationResult> => {
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
return { success: false, error: `HTTP ${response.status}: ${text.slice(0, 200)}` };
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { errors?: Array<{ message: string }> };
|
||||
if (data.errors && data.errors.length > 0) {
|
||||
return { success: false, error: data.errors.map((e) => e.message).join(', ') };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
try {
|
||||
let response = await this.fetchWithTimeout(urlWithOperation, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
await this.refreshQueryIds();
|
||||
queryId = await this.getQueryId('DeleteBookmark');
|
||||
urlWithOperation = `${TWITTER_API_BASE}/${queryId}/DeleteBookmark`;
|
||||
body = buildBody();
|
||||
|
||||
response = await this.fetchWithTimeout(urlWithOperation, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
const retry = await this.fetchWithTimeout(TWITTER_GRAPHQL_POST_URL, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
return parseResponse(retry);
|
||||
}
|
||||
}
|
||||
|
||||
return parseResponse(response);
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TwitterClientBookmarks;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export const FALLBACK_QUERY_IDS = {
|
||||
CreateTweet: 'TAJw1rBsjAtdNgTdlo2oeg',
|
||||
CreateRetweet: 'ojPdsZsimiJrUGLR1sjUtA',
|
||||
FavoriteTweet: 'lI07N6Otwv1PhnEgXILM7A',
|
||||
DeleteBookmark: 'Wlmlj2-xzyS1GN3a6cj-mQ',
|
||||
TweetDetail: '97JF30KziU00483E_8elBA',
|
||||
SearchTimeline: 'M1jEez78PEfVfbQLvlWMvQ',
|
||||
UserArticlesTweets: '8zBy9h4L90aDL02RsBcCFg',
|
||||
|
||||
@@ -191,6 +191,15 @@ export type TweetResult =
|
||||
error: string;
|
||||
};
|
||||
|
||||
export type BookmarkMutationResult =
|
||||
| {
|
||||
success: true;
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export interface UploadMediaResult {
|
||||
success: boolean;
|
||||
mediaId?: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AbstractConstructor } from './twitter-client-base.js';
|
||||
import { TwitterClientBase } from './twitter-client-base.js';
|
||||
import { type TwitterClientBookmarkMethods, withBookmarks } from './twitter-client-bookmarks.js';
|
||||
import { type TwitterClientListMethods, withLists } from './twitter-client-lists.js';
|
||||
import { type TwitterClientMediaMethods, withMedia } from './twitter-client-media.js';
|
||||
import { type TwitterClientPostingMethods, withPosting } from './twitter-client-posting.js';
|
||||
@@ -9,6 +10,7 @@ import { type TwitterClientTweetDetailMethods, withTweetDetails } from './twitte
|
||||
import { type TwitterClientUserMethods, withUsers } from './twitter-client-users.js';
|
||||
|
||||
type TwitterClientInstance = TwitterClientBase &
|
||||
TwitterClientBookmarkMethods &
|
||||
TwitterClientListMethods &
|
||||
TwitterClientMediaMethods &
|
||||
TwitterClientPostingMethods &
|
||||
@@ -18,12 +20,13 @@ type TwitterClientInstance = TwitterClientBase &
|
||||
TwitterClientUserMethods;
|
||||
|
||||
const MixedTwitterClient = withUsers(
|
||||
withLists(withTimelines(withSearch(withTweetDetails(withPosting(withMedia(TwitterClientBase)))))),
|
||||
withLists(withTimelines(withSearch(withTweetDetails(withPosting(withBookmarks(withMedia(TwitterClientBase))))))),
|
||||
) as AbstractConstructor<TwitterClientInstance>;
|
||||
|
||||
export class TwitterClient extends MixedTwitterClient {}
|
||||
|
||||
export type {
|
||||
BookmarkMutationResult,
|
||||
CurrentUserResult,
|
||||
FollowingResult,
|
||||
GetTweetResult,
|
||||
|
||||
Reference in New Issue
Block a user