Something went wrong. Try again.
an appview-less Bluesky client using Constellation and PDS Queries - https://reddwarf.app/ forked from whey.party/red-dwarf
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681import * as ATPAPI from "@atproto/api";import { infiniteQueryOptions, type QueryFunctionContext, queryOptions, useInfiniteQuery, useQuery, type UseQueryResult} from "@tanstack/react-query";import { useAtom } from "jotai";
import { constellationURLAtom, slingshotURLAtom } from "./atoms";
export function constructIdentityQuery(didorhandle?: string, slingshoturl?: string) { return queryOptions({ queryKey: ["identity", didorhandle], queryFn: async () => { if (!didorhandle) return undefined as undefined const res = await fetch( `https://${slingshoturl}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${encodeURIComponent(didorhandle)}` ); if (!res.ok) throw new Error("Failed to fetch post"); try { return (await res.json()) as { did: string; handle: string; pds: string; signing_key: string; }; } catch (_e) { return undefined; } }, staleTime: /*0,//*/ 5 * 60 * 1000, // 5 minutes gcTime: /*0//*/5 * 60 * 1000, });}export function useQueryIdentity(didorhandle: string): UseQueryResult< { did: string; handle: string; pds: string; signing_key: string; }, Error>;export function useQueryIdentity(): UseQueryResult< undefined, Error >export function useQueryIdentity(didorhandle?: string): UseQueryResult< { did: string; handle: string; pds: string; signing_key: string; } | undefined, Error >export function useQueryIdentity(didorhandle?: string) { const [slingshoturl] = useAtom(slingshotURLAtom) return useQuery(constructIdentityQuery(didorhandle, slingshoturl));}
export function constructPostQuery(uri?: string, slingshoturl?: string) { return queryOptions({ queryKey: ["post", uri], queryFn: async () => { if (!uri) return undefined as undefined const res = await fetch( `https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` ); let data: any; try { data = await res.json(); } catch { return undefined; } if (res.status === 400) return undefined; if (data?.error === "InvalidRequest" && data.message?.includes("Could not find repo")) { return undefined; // cache “not found” } try { if (!res.ok) throw new Error("Failed to fetch post"); return (data) as { uri: string; cid: string; value: any; }; } catch (_e) { return undefined; } }, retry: (failureCount, error) => { // dont retry 400 errors if ((error as any)?.message?.includes("400")) return false; return failureCount < 2; }, staleTime: /*0,//*/ 5 * 60 * 1000, // 5 minutes gcTime: /*0//*/5 * 60 * 1000, });}export function useQueryPost(uri: string): UseQueryResult< { uri: string; cid: string; value: ATPAPI.AppBskyFeedPost.Record; }, Error>;export function useQueryPost(): UseQueryResult< undefined, Error >export function useQueryPost(uri?: string): UseQueryResult< { uri: string; cid: string; value: ATPAPI.AppBskyFeedPost.Record; } | undefined, Error >export function useQueryPost(uri?: string) { const [slingshoturl] = useAtom(slingshotURLAtom) return useQuery(constructPostQuery(uri, slingshoturl));}
export function constructProfileQuery(uri?: string, slingshoturl?: string) { return queryOptions({ queryKey: ["profile", uri], queryFn: async () => { if (!uri) return undefined as undefined const res = await fetch( `https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` ); let data: any; try { data = await res.json(); } catch { return undefined; } if (res.status === 400) return undefined; if (data?.error === "InvalidRequest" && data.message?.includes("Could not find repo")) { return undefined; // cache “not found” } try { if (!res.ok) throw new Error("Failed to fetch post"); return (data) as { uri: string; cid: string; value: any; }; } catch (_e) { return undefined; } }, retry: (failureCount, error) => { // dont retry 400 errors if ((error as any)?.message?.includes("400")) return false; return failureCount < 2; }, staleTime: /*0,//*/ 5 * 60 * 1000, // 5 minutes gcTime: /*0//*/5 * 60 * 1000, });}export function useQueryProfile(uri: string): UseQueryResult< { uri: string; cid: string; value: ATPAPI.AppBskyActorProfile.Record; }, Error>;export function useQueryProfile(): UseQueryResult< undefined, Error>;export function useQueryProfile(uri?: string): UseQueryResult< { uri: string; cid: string; value: ATPAPI.AppBskyActorProfile.Record; } | undefined, Error >export function useQueryProfile(uri?: string) { const [slingshoturl] = useAtom(slingshotURLAtom) return useQuery(constructProfileQuery(uri, slingshoturl));}
// export function constructConstellationQuery(// method: "/links",// target: string,// collection: string,// path: string,// cursor?: string// ): QueryOptions<linksRecordsResponse, Error>;// export function constructConstellationQuery(// method: "/links/distinct-dids",// target: string,// collection: string,// path: string,// cursor?: string// ): QueryOptions<linksDidsResponse, Error>;// export function constructConstellationQuery(// method: "/links/count",// target: string,// collection: string,// path: string,// cursor?: string// ): QueryOptions<linksCountResponse, Error>;// export function constructConstellationQuery(// method: "/links/count/distinct-dids",// target: string,// collection: string,// path: string,// cursor?: string// ): QueryOptions<linksCountResponse, Error>;// export function constructConstellationQuery(// method: "/links/all",// target: string// ): QueryOptions<linksAllResponse, Error>;export function constructConstellationQuery(query?:{ constellation: string, method: | "/links" | "/links/distinct-dids" | "/links/count" | "/links/count/distinct-dids" | "/links/all" | "undefined", target: string, collection?: string, path?: string, cursor?: string, dids?: string[]}) { // : QueryOptions< // | linksRecordsResponse // | linksDidsResponse // | linksCountResponse // | linksAllResponse // | undefined, // Error // > return queryOptions({ queryKey: ["constellation", query?.method, query?.target, query?.collection, query?.path, query?.cursor, query?.dids] as const, queryFn: async () => { if (!query || query.method === "undefined") return undefined as undefined const method = query.method const target = query.target const collection = query?.collection const path = query?.path const cursor = query.cursor const dids = query?.dids const res = await fetch( `https://${query.constellation}${method}?target=${encodeURIComponent(target)}${collection ? `&collection=${encodeURIComponent(collection)}` : ""}${path ? `&path=${encodeURIComponent(path)}` : ""}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}${dids ? dids.map((did) => `&did=${encodeURIComponent(did)}`).join("") : ""}` ); if (!res.ok) throw new Error("Failed to fetch post"); try { switch (method) { case "/links": return (await res.json()) as linksRecordsResponse; case "/links/distinct-dids": return (await res.json()) as linksDidsResponse; case "/links/count": return (await res.json()) as linksCountResponse; case "/links/count/distinct-dids": return (await res.json()) as linksCountResponse; case "/links/all": return (await res.json()) as linksAllResponse; default: return undefined; } } catch (_e) { return undefined; } }, // enforce short lifespan staleTime: /*0,//*/ 5 * 60 * 1000, // 5 minutes gcTime: /*0//*/5 * 60 * 1000, });}export function useQueryConstellation(query: { method: "/links"; target: string; collection: string; path: string; cursor?: string; dids?: string[];}): UseQueryResult<linksRecordsResponse, Error>;export function useQueryConstellation(query: { method: "/links/distinct-dids"; target: string; collection: string; path: string; cursor?: string;}): UseQueryResult<linksDidsResponse, Error>;export function useQueryConstellation(query: { method: "/links/count"; target: string; collection: string; path: string; cursor?: string;}): UseQueryResult<linksCountResponse, Error>;export function useQueryConstellation(query: { method: "/links/count/distinct-dids"; target: string; collection: string; path: string; cursor?: string;}): UseQueryResult<linksCountResponse, Error>;export function useQueryConstellation(query: { method: "/links/all"; target: string;}): UseQueryResult<linksAllResponse, Error>;export function useQueryConstellation(): undefined;export function useQueryConstellation(query: { method: "undefined"; target: string;}): undefined;export function useQueryConstellation(query?: { method: | "/links" | "/links/distinct-dids" | "/links/count" | "/links/count/distinct-dids" | "/links/all" | "undefined"; target: string; collection?: string; path?: string; cursor?: string; dids?: string[];}): | UseQueryResult< | linksRecordsResponse | linksDidsResponse | linksCountResponse | linksAllResponse | undefined, Error > | undefined { //if (!query) return; const [constellationurl] = useAtom(constellationURLAtom) return useQuery( constructConstellationQuery(query && {constellation: constellationurl, ...query}) );}
type linksRecord = { did: string; collection: string; rkey: string;};export type linksRecordsResponse = { total: string; linking_records: linksRecord[]; cursor?: string;};type linksDidsResponse = { total: string; linking_dids: string[]; cursor?: string;};type linksCountResponse = { total: string;};export type linksAllResponse = { links: Record< string, Record< string, { records: number; distinct_dids: number; } > >;};
export function constructFeedSkeletonQuery(options?: { feedUri: string; agent?: ATPAPI.Agent; isAuthed: boolean; pdsUrl?: string; feedServiceDid?: string;}) { return queryOptions({ // The query key includes all dependencies to ensure it refetches when they change queryKey: ["feedSkeleton", options?.feedUri, { isAuthed: options?.isAuthed, did: options?.agent?.did }], queryFn: async () => { if (!options) return undefined as undefined const { feedUri, agent, isAuthed, pdsUrl, feedServiceDid } = options; if (isAuthed) { // Authenticated flow if (!agent || !pdsUrl || !feedServiceDid) { throw new Error("Missing required info for authenticated feed fetch."); } const url = `${pdsUrl}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}`; const res = await agent.fetchHandler(url, { method: "GET", headers: { "atproto-proxy": `${feedServiceDid}#bsky_fg`, "Content-Type": "application/json", }, }); if (!res.ok) throw new Error(`Authenticated feed fetch failed: ${res.statusText}`); return (await res.json()) as ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema; } else { // Unauthenticated flow (using a public PDS/AppView) const url = `https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}`; const res = await fetch(url); if (!res.ok) throw new Error(`Public feed fetch failed: ${res.statusText}`); return (await res.json()) as ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema; } }, //enabled: !!feedUri && (isAuthed ? !!agent && !!pdsUrl && !!feedServiceDid : true), });}
export function useQueryFeedSkeleton(options?: { feedUri: string; agent?: ATPAPI.Agent; isAuthed: boolean; pdsUrl?: string; feedServiceDid?: string;}) { return useQuery(constructFeedSkeletonQuery(options));}
export function constructPreferencesQuery(agent?: ATPAPI.Agent | undefined, pdsUrl?: string | undefined) { return queryOptions({ queryKey: ['preferences', agent?.did], queryFn: async () => { if (!agent || !pdsUrl) throw new Error("Agent or PDS URL not available"); const url = `${pdsUrl}/xrpc/app.bsky.actor.getPreferences`; const res = await agent.fetchHandler(url, { method: "GET" }); if (!res.ok) throw new Error("Failed to fetch preferences"); return res.json(); }, });}export function useQueryPreferences(options: { agent?: ATPAPI.Agent | undefined, pdsUrl?: string | undefined}) { return useQuery(constructPreferencesQuery(options.agent, options.pdsUrl));}
export function constructArbitraryQuery(uri?: string, slingshoturl?: string) { return queryOptions({ queryKey: ["arbitrary", uri], queryFn: async () => { if (!uri) return undefined as undefined const res = await fetch( `https://${slingshoturl}/xrpc/com.bad-example.repo.getUriRecord?at_uri=${encodeURIComponent(uri)}` ); let data: any; try { data = await res.json(); } catch { return undefined; } if (res.status === 400) return undefined; if (data?.error === "InvalidRequest" && data.message?.includes("Could not find repo")) { return undefined; // cache “not found” } try { if (!res.ok) throw new Error("Failed to fetch post"); return (data) as { uri: string; cid: string; value: any; }; } catch (_e) { return undefined; } }, retry: (failureCount, error) => { // dont retry 400 errors if ((error as any)?.message?.includes("400")) return false; return failureCount < 2; }, staleTime: /*0,//*/ 5 * 60 * 1000, // 5 minutes gcTime: /*0//*/5 * 60 * 1000, });}export function useQueryArbitrary(uri: string): UseQueryResult< { uri: string; cid: string; value: any; }, Error>;export function useQueryArbitrary(): UseQueryResult< undefined, Error>;export function useQueryArbitrary(uri?: string): UseQueryResult< { uri: string; cid: string; value: any; } | undefined, Error>;export function useQueryArbitrary(uri?: string) { const [slingshoturl] = useAtom(slingshotURLAtom) return useQuery(constructArbitraryQuery(uri, slingshoturl));}
export function constructFallbackNothingQuery(){ return queryOptions({ queryKey: ["nothing"], queryFn: async () => { return undefined }, });}
type ListRecordsResponse = { cursor?: string; records: { uri: string; cid: string; value: ATPAPI.AppBskyFeedPost.Record; }[];};
export function constructAuthorFeedQuery(did: string, pdsUrl: string) { return queryOptions({ queryKey: ['authorFeed', did], queryFn: async ({ pageParam }: QueryFunctionContext) => { const limit = 25; const cursor = pageParam as string | undefined; const cursorParam = cursor ? `&cursor=${cursor}` : ''; const url = `${pdsUrl}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=app.bsky.feed.post&limit=${limit}${cursorParam}`; const res = await fetch(url); if (!res.ok) throw new Error("Failed to fetch author's posts"); return res.json() as Promise<ListRecordsResponse>; }, });}
export function useInfiniteQueryAuthorFeed(did: string | undefined, pdsUrl: string | undefined) { const { queryKey, queryFn } = constructAuthorFeedQuery(did!, pdsUrl!); return useInfiniteQuery({ queryKey, queryFn, initialPageParam: undefined as never, // ???? what is this shit getNextPageParam: (lastPage) => lastPage.cursor as null | undefined, enabled: !!did && !!pdsUrl, });}
type FeedSkeletonPage = ATPAPI.AppBskyFeedGetFeedSkeleton.OutputSchema;
export function constructInfiniteFeedSkeletonQuery(options: { feedUri: string; agent?: ATPAPI.Agent; isAuthed: boolean; pdsUrl?: string; feedServiceDid?: string;}) { const { feedUri, agent, isAuthed, pdsUrl, feedServiceDid } = options; return queryOptions({ queryKey: ["feedSkeleton", feedUri, { isAuthed, did: agent?.did }], queryFn: async ({ pageParam }: QueryFunctionContext): Promise<FeedSkeletonPage> => { const cursorParam = pageParam ? `&cursor=${pageParam}` : ""; if (isAuthed) { if (!agent || !pdsUrl || !feedServiceDid) { throw new Error("Missing required info for authenticated feed fetch."); } const url = `${pdsUrl}/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`; const res = await agent.fetchHandler(url, { method: "GET", headers: { "atproto-proxy": `${feedServiceDid}#bsky_fg`, "Content-Type": "application/json", }, }); if (!res.ok) throw new Error(`Authenticated feed fetch failed: ${res.statusText}`); return (await res.json()) as FeedSkeletonPage; } else { const url = `https://discover.bsky.app/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`; const res = await fetch(url); if (!res.ok) throw new Error(`Public feed fetch failed: ${res.statusText}`); return (await res.json()) as FeedSkeletonPage; } }, });}
export function useInfiniteQueryFeedSkeleton(options: { feedUri: string; agent?: ATPAPI.Agent; isAuthed: boolean; pdsUrl?: string; feedServiceDid?: string;}) { const { queryKey, queryFn } = constructInfiniteFeedSkeletonQuery(options); return {...useInfiniteQuery({ queryKey, queryFn, initialPageParam: undefined as never, getNextPageParam: (lastPage) => lastPage.cursor as null | undefined, staleTime: Infinity, refetchOnWindowFocus: false, enabled: !!options.feedUri && (options.isAuthed ? !!options.agent && !!options.pdsUrl && !!options.feedServiceDid : true), }), queryKey: queryKey};}
export function yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(query?: { constellation: string, method: '/links' target?: string collection: string path: string}) { console.log( 'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks', query, )
return infiniteQueryOptions({ enabled: !!query?.target, queryKey: [ 'reddwarf_constellation', query?.method, query?.target, query?.collection, query?.path, ] as const,
queryFn: async ({pageParam}: {pageParam?: string}) => { if (!query || !query?.target) return undefined
const method = query.method const target = query.target const collection = query.collection const path = query.path const cursor = pageParam
const res = await fetch( `https://${query.constellation}${method}?target=${encodeURIComponent(target)}${ collection ? `&collection=${encodeURIComponent(collection)}` : '' }${path ? `&path=${encodeURIComponent(path)}` : ''}${ cursor ? `&cursor=${encodeURIComponent(cursor)}` : '' }`, )
if (!res.ok) throw new Error('Failed to fetch')
return (await res.json()) as linksRecordsResponse },
getNextPageParam: lastPage => { return (lastPage as any)?.cursor ?? undefined }, initialPageParam: undefined, staleTime: 5 * 60 * 1000, gcTime: 5 * 60 * 1000, })}