Skip to content
← Public packages

@kentcdodds/x

X API v2 helpers for tweets, search, legacy DMs, and encrypted X Chat via a Fly XDK sidecar.

src/types.ts

450 lines · 11.1 KB · TypeScript
import type { QueryInput, XAuthMode } from './openapi-client.ts'

export type { QueryInput, XAuthMode }

export type JsonRecord = Record<string, unknown>

/**
 * Multi-account selection for OAuth user-context calls.
 * Convention: omit for default integration `x`; `account: 'brand'` → `x-brand`.
 * Prefer `integration` when you already know the exact saved integration name.
 */
export type XAccountParams = {
	account?: string
	integration?: string
}

export type ResolvedXOAuth = {
	integrationName: string
	/** Null for the default `x` integration. */
	account: string | null
	/** Legacy secret mirror name when present; null for connection-stored tokens. */
	accessTokenSecretName: string | null
	/** Legacy secret mirror name when present; null for connection-stored tokens. */
	refreshTokenSecretName: string | null
	/** Non-secret OAuth client id, stored inline on the integration's OAuth app. */
	clientId: string
	accessTokenPlaceholder: string | null
	refreshTokenPlaceholder: string | null
}

export type XAccountSummary = {
	/** Null for the default `x` integration. */
	account: string | null
	integration: string
	default: boolean
	accessTokenSecretName: string | null
	useWhen: string
}

export type XUser = JsonRecord & {
	id?: string
	name?: string
	username?: string
	verified?: boolean
	description?: string
	created_at?: string
	public_metrics?: JsonRecord
	url?: string
	location?: string
}

export type XPost = JsonRecord & {
	id?: string
	text?: string
	author_id?: string
	created_at?: string
	conversation_id?: string
	public_metrics?: JsonRecord
	referenced_tweets?: unknown[]
	entities?: JsonRecord
	note_tweet?: JsonRecord
}

export type XApiResponse<T = unknown> = {
	data?: T
	includes?: JsonRecord
	meta?: JsonRecord
	errors?: unknown[]
	title?: string
	detail?: string
	type?: string
	status?: number
	rateLimit?: XRateLimitInfo
}

export type XRateLimitInfo = {
	limit?: string | null
	remaining?: string | null
	reset?: string | null
}

export type XDryRunResult = {
	dryRun: true
	action?: string
	method?: string
	url?: string
	authMode?: XAuthMode
	body?: unknown
	payload?: JsonRecord
	requiresConfirm: true
	/** Present on create-post / reply-post dry-run and confirm results. */
	mode?: 'reply' | 'standalone'
	/** Parent tweet id when mode is reply. */
	inReplyToTweetId?: string
	parentId?: string
	/** Reply text before leading @handle strip (create-post / reply-post). */
	originalText?: string
	/** Text that will be / was sent (after reply @handle strip when applicable). */
	text?: string
	/** Leading @handles removed because X auto-prefixes reply-chain mentions. */
	strippedHandles?: string[]
	/** User ids omitted from X auto reply-chain mentions (create-post / reply-post). */
	excludeReplyUserIds?: string[]
	/** Snake alias echoed alongside excludeReplyUserIds. */
	exclude_reply_user_ids?: string[]
}

export type XRequestParams = XAccountParams & {
	path: string
	method?: string
	query?: QueryInput
	body?: JsonRecord | null
	authMode?: XAuthMode
	dryRun?: boolean
	confirm?: boolean
	refreshOnUnauthorized?: boolean
	accessToken?: string
}

export type XRefreshTokenParams = XAccountParams & {
	internal?: boolean
}

export type XGetMeParams = XAccountParams & {
	userFields?: string
}

export type XGetUserByUsernameParams = XAccountParams & {
	username: string
	userFields?: string
	authMode?: XAuthMode
}

export type XSearchRecentParams = XAccountParams & {
	query: string
	maxResults?: number
	max_results?: number
	/** Maps to X API `tweet.fields`. */
	postFields?: string
	expansions?: string
	userFields?: string
	authMode?: XAuthMode
}

export type XGetUserPostsParams = XAccountParams & {
	id: string
	maxResults?: number
	max_results?: number
	paginationToken?: string
	pagination_token?: string
	/** Maps to X API `tweet.fields` (resource is still tweets). */
	postFields?: string
	expansions?: string
	/**
	 * When true, request `exclude=retweets,replies` and also drop residual
	 * `RT @` / leading-`@` reply patterns that the exclude query sometimes leaves.
	 */
	originalsOnly?: boolean
	/** Passed through as X API `exclude` (e.g. `retweets,replies`). Ignored when originalsOnly sets it. */
	exclude?: string
	authMode?: XAuthMode
}

export type XGetPostParams = XAccountParams & {
	id?: string
	postId?: string
	/** x.com / twitter.com status URL — id is parsed from the path. */
	url?: string
	/** Maps to X API `tweet.fields`. */
	postFields?: string
	expansions?: string
	userFields?: string
	authMode?: XAuthMode
	/**
	 * When true, also GET `/tweets/:id/quote_tweets` (small page).
	 * Default false — quote_tweets is rate-limited; expand referenced quoted post via expansions instead.
	 */
	includeQuotePosts?: boolean
	/** max_results for quote_tweets when includeQuotePosts. Default 10, hard cap 25. */
	quoteMaxResults?: number
}

export type XGetPostThreadParams = XAccountParams & {
	id?: string
	postId?: string
	url?: string
	/** Bounded search-recent page size for conversation replies. Default 20, hard cap 50. */
	maxResults?: number
	max_results?: number
	postFields?: string
	expansions?: string
	userFields?: string
	authMode?: XAuthMode
}

export type XPublicMetrics = {
	impression_count?: number
	like_count?: number
	reply_count?: number
	retweet_count?: number
	bookmark_count?: number
	quote_count?: number
	[key: string]: unknown
}

export type XPostMetricsInput = {
	id?: string
	public_metrics?: XPublicMetrics | JsonRecord | null
	[key: string]: unknown
}

export type XSummarizePostMetricsSort =
	| 'likesPer1k'
	| 'bookmarksPer1k'
	| 'repliesPer1k'
	| 'quotesPer1k'
	| 'engagementPer1k'
	| 'impressions'
	| 'likes'
	| 'replies'
	| 'retweets'
	| 'bookmarks'
	| 'quotes'

export type XSummarizePostMetricsParams = XAccountParams & {
	posts?: XPostMetricsInput[]
	post?: XPostMetricsInput
	ids?: string[]
	id?: string
	url?: string
	sort?: XSummarizePostMetricsSort
	/**
	 * `lowLikeHighImpression`: impressions at/above median AND likesPer1k
	 * strictly below median (among rows with impressions > 0).
	 */
	filter?: 'lowLikeHighImpression'
	authMode?: XAuthMode
}

export type XCreatePostParams = XAccountParams & {
	text: string
	/** X API reply object. Prefer this shape. */
	reply?: {
		in_reply_to_tweet_id: string
		exclude_reply_user_ids?: string[]
		excludeReplyUserIds?: string[]
	}
	/** Alias → reply.in_reply_to_tweet_id (mapped; unknown keys still rejected). */
	replyTo?: string
	/** Alias → reply.in_reply_to_tweet_id */
	inReplyTo?: string
	/** Alias → reply.in_reply_to_tweet_id */
	inReplyToTweetId?: string
	/** Alias → reply.exclude_reply_user_ids (omit users from auto reply-chain mentions). */
	excludeReplyUserIds?: string[]
	/** Alias → reply.exclude_reply_user_ids */
	exclude_reply_user_ids?: string[]
	quote_tweet_id?: string
	quotePostId?: string
	media?: JsonRecord
	poll?: JsonRecord
	direct_message_deep_link?: string
	directMessageDeepLink?: string
	for_super_followers_only?: boolean
	forSuperFollowersOnly?: boolean
	dryRun?: boolean
	confirm?: boolean
}

export type XDeletePostParams = XAccountParams & {
	id: string
	dryRun?: boolean
	confirm?: boolean
}

export type XPostActionParams = XAccountParams & {
	/** Post/status id (X API resource is still a tweet). */
	postId?: string
	id?: string
	userId?: string
	dryRun?: boolean
	confirm?: boolean
}

export type XUserActionParams = XAccountParams & {
	username?: string
	userId?: string
	targetUserId?: string
	id?: string
	dryRun?: boolean
	confirm?: boolean
}

export type XDmEvent = JsonRecord & {
	id?: string
	event_type?: string
	text?: string
	sender_id?: string
	created_at?: string
	dm_conversation_id?: string
	attachments?: JsonRecord
	referenced_tweets?: unknown[]
}

/**
 * DM target selection: exactly one of `conversationId`, `participantId`,
 * `username`, or `participantIds` (group creation).
 */
export type XDmTargetParams = {
	conversationId?: string
	dm_conversation_id?: string
	participantId?: string
	participant_id?: string
	username?: string
	/** Two or more user ids — creates a new group DM conversation. */
	participantIds?: string[]
	participant_ids?: string[]
}

export type XSendDmParams = XAccountParams &
	XDmTargetParams & {
		text?: string
		/** Shorthand for attachments: [{ media_id }]. */
		mediaId?: string
		media_id?: string
		attachments?: JsonRecord[]
		dryRun?: boolean
		confirm?: boolean
		/**
		 * Optional stable key for one logical DM. Same `(account, idempotencyKey)`
		 * within 7 days returns the prior successful send result instead of sending
		 * again. Different keys are different sends. dryRun / unconfirmed calls ignore this.
		 */
		idempotencyKey?: string
	}

export type XDmReadParams = XAccountParams & {
	maxResults?: number
	max_results?: number
	paginationToken?: string
	pagination_token?: string
	/** Comma-separated subset of MessageCreate,ParticipantsJoin,ParticipantsLeave. */
	eventTypes?: string
	event_types?: string
	dmEventFields?: string
	expansions?: string
	userFields?: string
}

export type XListDmEventsParams = XDmReadParams

export type XGetDmConversationParams = XDmReadParams & {
	conversationId?: string
	dm_conversation_id?: string
	participantId?: string
	participant_id?: string
	username?: string
}

export type XChatSidecarParams = {
	/** Override the Fly sidecar origin. Else package storage `xChatSidecarUrl`, else owner-config default. */
	sidecarUrl?: string
}

export type XListChatConversationsParams = XAccountParams &
	XChatSidecarParams & {
		maxResults?: number
		max_results?: number
		paginationToken?: string
		pagination_token?: string
	}

export type XGetChatConversationParams = XAccountParams &
	XChatSidecarParams & {
		conversationId?: string
		conversation_id?: string
		participantId?: string
		participant_id?: string
		username?: string
		maxResults?: number
		max_results?: number
		paginationToken?: string
		pagination_token?: string
	}

export type XSendChatParams = XAccountParams &
	XChatSidecarParams & {
		conversationId?: string
		conversation_id?: string
		participantId?: string
		participant_id?: string
		username?: string
		text?: string
		dryRun?: boolean
		confirm?: boolean
		/**
		 * Optional stable key for one logical Chat message. Same `(account, idempotencyKey)`
		 * within 7 days returns the prior successful send result instead of sending
		 * again. Different keys are different sends. dryRun / unconfirmed calls ignore this.
		 */
		idempotencyKey?: string
	}

export type XChatSigningKey = {
	user_id: string
	public_key_version: string
	public_key: string
	identity_public_key: string
	identity_public_key_signature: string
}

export type XChatPublicKeyRecord = JsonRecord & {
	public_key?: string
	signing_public_key?: string
	identity_public_key?: string
	identity_public_key_signature?: string
	public_key_version?: string | number
	juicebox_config?: JsonRecord | string
}

export type XChatConversation = JsonRecord & {
	id?: string
	type?: string
	participant_ids?: string[]
	is_muted?: boolean
}

export type XChatMessage = {
	id?: string
	type?: string
	senderId?: string
	createdAt?: string
	text?: string | null
	conversationId?: string
}

export type XUserSummary = {
	id?: string
	username?: string
	name?: string
}

export type XSmokeTestResult = {
	ok: boolean
	bearer?: { user: XUserSummary | null }
	oauth?: { user: XUserSummary | null }
	rateLimited?: boolean
	note?: string
	status?: number
}