export type Did = string; export type Handle = string; export type AtUri = string; export type Cid = string; export type Cursor = string; export interface Label { cts: string; src: string; uri: string; val: string; neg?: boolean; ver?: number; } export interface SelfLabel { $type: 'com.atproto.label.defs#selfLabel'; val: string; } export type LabelValue = Label | SelfLabel | { $type: string; val?: string; src?: string; neg?: boolean }; export interface ResolvedLabel { val: string; displayName: string; labelerName: string; labelerAvatar?: string; } export interface ProfileViewBasic { did: Did; handle: Handle; displayName?: string; avatar?: string; labels?: LabelValue[]; pronouns?: string; website?: string; /** If true, this profile was constructed from PDS data because the appView * was unavailable or returned a suspended/takedown status. */ suspended?: boolean; viewer?: { muted?: boolean; blockedBy?: boolean; following?: AtUri; followedBy?: AtUri; }; } export interface EmbedAspectRatio { width: number; height: number; } export interface EmbedImageView { $type: 'app.bsky.embed.images#view'; images: { thumb: string; fullsize: string; alt: string; aspectRatio?: EmbedAspectRatio; }[]; } export interface EmbedVideoView { $type: 'app.bsky.embed.video#view'; cid: string; playlist: string; thumbnail?: string; alt?: string; aspectRatio?: EmbedAspectRatio; } export interface EmbedExternalView { $type: 'app.bsky.embed.external#view'; external: { uri: string; title: string; description: string; thumb?: string; }; } export interface EmbedViewRecord { $type: 'app.bsky.embed.record#viewRecord'; uri: AtUri; cid: Cid; author: ProfileViewBasic; value: { text: string; createdAt: string; embed?: unknown; facets?: unknown[]; }; labels?: LabelValue[]; replyCount?: number; repostCount?: number; likeCount?: number; quoteCount?: number; embeds?: unknown[]; indexedAt: string; } export interface EmbedRecordViewNotFound { $type: 'app.bsky.embed.record#viewNotFound'; uri: AtUri; notFound: true; } export interface EmbedRecordViewBlocked { $type: 'app.bsky.embed.record#viewBlocked'; uri: AtUri; blocked: true; } export interface EmbedRecordView { $type: 'app.bsky.embed.record#view'; record: EmbedViewRecord | EmbedRecordViewNotFound | EmbedRecordViewBlocked | { $type: string }; } export interface EmbedRecordWithMediaView { $type: 'app.bsky.embed.recordWithMedia#view'; record: EmbedRecordView; media: EmbedImageView | EmbedVideoView | EmbedExternalView | { $type: string }; } export type EmbedView = | EmbedImageView | EmbedVideoView | EmbedExternalView | EmbedRecordView | EmbedRecordWithMediaView | { $type: string }; export interface ProfileViewDetailed { did: Did; handle: Handle; displayName?: string; description?: string; avatar?: string; banner?: string; followsCount?: number; followersCount?: number; postsCount?: number; labels?: Label[]; pronouns?: string; website?: string; indexedAt?: string; /** If true, this profile was fetched from the PDS directly because the * appView was unavailable or returned a takedown/suspended status. */ suspended?: boolean; pinnedPost?: { uri: AtUri; cid: Cid; }; viewer?: { muted?: boolean; mutedByList?: unknown; blockedBy?: boolean; blocking?: AtUri; blockingByList?: unknown; following?: AtUri; followedBy?: AtUri; knownFollowers?: { count: number; followers: ProfileViewBasic[]; }; }; } export interface FacetByteSlice { byteStart: number; byteEnd: number; } export interface FacetMention { $type: 'app.bsky.richtext.facet#mention'; did: string; } export interface FacetLink { $type: 'app.bsky.richtext.facet#link'; uri: string; } export interface FacetTag { $type: 'app.bsky.richtext.facet#tag'; tag: string; } export type FacetFeature = FacetMention | FacetLink | FacetTag | { $type: string }; export interface Facet { index: FacetByteSlice; features: FacetFeature[]; } export interface PostView { uri: AtUri; cid: Cid; author: ProfileViewBasic; record: { $type: string; text: string; createdAt: string; embed?: unknown; reply?: { root: { uri: AtUri; cid: Cid }; parent: { uri: AtUri; cid: Cid }; }; langs?: string[]; facets?: Facet[]; }; embed?: EmbedView; replyCount?: number; repostCount?: number; likeCount?: number; quoteCount?: number; indexedAt: string; labels?: Label[]; viewer?: { repost?: AtUri; like?: AtUri; threadMuted?: boolean; }; } export interface ReplyRef { root: PostView | NotFoundPost | BlockedPost | { $type: string }; parent: PostView | NotFoundPost | BlockedPost | { $type: string }; grandparentAuthor?: ProfileViewBasic; } export interface FeedViewPost { post: PostView; reply?: ReplyRef; reason?: { $type: string; by: ProfileViewBasic; indexedAt: string; }; } export interface GetTimelineResponse { cursor?: Cursor; feed: FeedViewPost[]; } export interface ThreadViewPost { $type: 'app.bsky.feed.defs#threadViewPost'; post: PostView; parent?: ThreadViewPost | NotFoundPost | BlockedPost | { $type: string }; replies?: (ThreadViewPost | NotFoundPost | BlockedPost | { $type: string })[]; } export interface NotFoundPost { $type: 'app.bsky.feed.defs#notFoundPost'; uri: AtUri; notFound: true; } export interface BlockedPost { $type: 'app.bsky.feed.defs#blockedPost'; uri: AtUri; blocked: true; } /** * A post constructed from a raw repo record when the appView is unavailable * or the author's PDS is suspended/deactivated. Contains the essential data * needed to render the post, but may lack appView-enriched fields like * likeCount, replyCount, etc. */ export interface SuspendedPost { $type: 'foxsky.feed.defs#suspendedPost'; uri: AtUri; cid: Cid; author: ProfileViewBasic; record: { $type: string; text: string; createdAt: string; embed?: unknown; reply?: { root: { uri: AtUri; cid: Cid }; parent: { uri: AtUri; cid: Cid }; }; langs?: string[]; facets?: Facet[]; }; embed?: EmbedView; replyCount?: number; repostCount?: number; likeCount?: number; quoteCount?: number; indexedAt: string; labels?: Label[]; viewer?: { repost?: AtUri; like?: AtUri; threadMuted?: boolean; }; } export interface GetPostThreadResponse { thread: ThreadViewPost | NotFoundPost | BlockedPost | SuspendedPost | { $type: string }; } export function isThreadViewPost(t: unknown): t is ThreadViewPost { return typeof t === 'object' && t !== null && (t as { $type?: string }).$type === 'app.bsky.feed.defs#threadViewPost'; } export function isBlockedPost(t: unknown): t is BlockedPost { return typeof t === 'object' && t !== null && (t as { $type?: string }).$type === 'app.bsky.feed.defs#blockedPost'; } export function isNotFoundPost(t: unknown): t is NotFoundPost { return typeof t === 'object' && t !== null && (t as { $type?: string }).$type === 'app.bsky.feed.defs#notFoundPost'; } export function isSuspendedPost(t: unknown): t is SuspendedPost { return typeof t === 'object' && t !== null && (t as { $type?: string }).$type === 'foxsky.feed.defs#suspendedPost'; } /** Convert a SuspendedPost into a ThreadViewPost wrapper so the rest of the * thread rendering pipeline works unchanged. */ export function suspendedToThread(sp: SuspendedPost): ThreadViewPost { return { $type: 'app.bsky.feed.defs#threadViewPost', post: sp, parent: undefined, replies: undefined, } as ThreadViewPost; } export interface BlobRef { $type: 'blob'; ref: { $link: string }; mimeType: string; size: number; } export interface UploadBlobResponse { blob: BlobRef; } export interface ActorTypeaheadResult { did: Did; handle: Handle; displayName?: string; avatar?: string; } export interface SearchActorsTypeaheadResponse { actors: ActorTypeaheadResult[]; } export interface FeedGeneratorInfo { uri: AtUri; cid: Cid; did: Did; creator: ProfileViewBasic & { displayName?: string; description?: string; avatar?: string; }; displayName: string; description?: string; avatar?: string; likeCount?: number; indexedAt: string; viewer?: { like?: AtUri; }; } export interface SavedFeedEntry { id: string; type: 'feed' | 'list' | 'timeline' | string; value: string; pinned: boolean; } export interface GetFeedResponse { cursor?: Cursor; feed: FeedViewPost[]; } export interface SearchPostsResponse { cursor?: Cursor; hitsTotal?: number; posts: PostView[]; } export interface ActorSearchResult { did: Did; handle: Handle; displayName?: string; description?: string; avatar?: string; indexedAt?: string; } export interface SearchActorsResponse { cursor?: Cursor; actors: ActorSearchResult[]; } export type NotificationReason = | 'like' | 'repost' | 'follow' | 'mention' | 'reply' | 'quote' | 'starterpack-joined' | 'verified' | 'unverified' | 'like-via-repost' | 'repost-via-repost' | 'subscribed-post' | 'contact-match'; export interface Notification { uri: AtUri; cid: Cid; author: ProfileViewBasic & { description?: string; indexedAt?: string; }; reason: NotificationReason; reasonSubject?: string; record: Record; isRead: boolean; indexedAt: string; labels?: Label[]; priority?: boolean; seenAt?: string; } export interface ListNotificationsResponse { cursor?: Cursor; notifications: Notification[]; priority: boolean; } export interface UnreadCountResponse { count: number; } export interface SessionData { did: Did; handle: Handle; accessJwt: string; refreshJwt: string; } export interface BookmarkItem { subject: { uri: AtUri; cid: Cid }; createdAt: string; item: PostView | { $type: string; uri: AtUri; notFound?: true; blocked?: boolean }; } export interface GetBookmarksResponse { cursor?: Cursor; bookmarks: BookmarkItem[]; } export interface FeedGeneratorView { uri: AtUri; cid: Cid; did: Did; creator: { did: Did; handle: Handle; displayName?: string; avatar?: string; }; displayName: string; description?: string; descriptionFacets?: Facet[]; avatar?: string; likeCount?: number; acceptsInteractions?: boolean; labels?: Label[]; indexedAt: string; viewer?: { like?: AtUri; }; } export interface GetActorFeedsResponse { cursor?: Cursor; feeds: FeedGeneratorView[]; } export interface SearchFeedsResponse { cursor?: Cursor; feeds: FeedGeneratorView[]; }