forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {createContext, useContext, useMemo} from 'react'
2import {QueryClient, useQuery} from '@tanstack/react-query'
3
4import {useIsBskyTeam} from '#/lib/hooks/useIsBskyTeam'
5import {
6 convertBskyAppUrlIfNeeded,
7 isBskyCustomFeedUrl,
8 makeRecordUri,
9} from '#/lib/strings/url-helpers'
10import {LIVE_EVENTS_URL} from '#/env'
11import {useLiveEventPreferences} from '#/features/liveEvents/preferences'
12import {type LiveEventsWorkerResponse} from '#/features/liveEvents/types'
13import {useDevMode} from '#/storage/hooks/dev-mode'
14
15const qc = new QueryClient()
16const liveEventsQueryKey = ['live-events']
17
18export const DEFAULT_LIVE_EVENTS = {
19 feeds: [],
20}
21
22async function fetchLiveEvents(): Promise<LiveEventsWorkerResponse | null> {
23 try {
24 const res = await fetch(`${LIVE_EVENTS_URL}/config`)
25 if (!res.ok) return null
26 const data = await res.json()
27 return data
28 } catch {
29 return null
30 }
31}
32
33const Context = createContext<LiveEventsWorkerResponse>(DEFAULT_LIVE_EVENTS)
34
35export function Provider({children}: React.PropsWithChildren<{}>) {
36 const [isDevMode] = useDevMode()
37 const isBskyTeam = useIsBskyTeam()
38 const {data} = useQuery(
39 {
40 // keep this, prefectching handles initial load
41 staleTime: 1000 * 15,
42 queryKey: liveEventsQueryKey,
43 refetchInterval: 1000 * 60 * 5,
44 async queryFn() {
45 return fetchLiveEvents()
46 },
47 },
48 qc,
49 )
50
51 const ctx = useMemo(() => {
52 if (!data) return DEFAULT_LIVE_EVENTS
53 const feeds = data.feeds.filter(f => {
54 if (f.preview && !isBskyTeam) return false
55 return true
56 })
57 return {
58 ...data,
59 // only one at a time for now, unless bsky team and dev mode
60 feeds: isBskyTeam && isDevMode ? feeds : feeds.slice(0, 1),
61 }
62 }, [data, isBskyTeam, isDevMode])
63
64 return <Context.Provider value={ctx}>{children}</Context.Provider>
65}
66
67export async function prefetchLiveEvents() {
68 const data = await fetchLiveEvents()
69 if (data) {
70 qc.setQueryData(liveEventsQueryKey, data)
71 }
72}
73
74export function useLiveEvents() {
75 const ctx = useContext(Context)
76 if (!ctx) {
77 throw new Error('useLiveEventsContext must be used within a Provider')
78 }
79 return ctx
80}
81
82export function useUserPreferencedLiveEvents() {
83 const events = useLiveEvents()
84 const {data, isLoading} = useLiveEventPreferences()
85 if (isLoading) return DEFAULT_LIVE_EVENTS
86 const {hideAllFeeds, hiddenFeedIds} = data
87 return {
88 ...events,
89 feeds: hideAllFeeds
90 ? []
91 : events.feeds.filter(f => {
92 const hidden = f?.id ? hiddenFeedIds.includes(f?.id || '') : false
93 return !hidden
94 }),
95 }
96}
97
98export function useActiveLiveEventFeedUris() {
99 const {feeds} = useLiveEvents()
100
101 return new Set(
102 feeds
103 // insurance
104 .filter(f => isBskyCustomFeedUrl(f.url))
105 .map(f => {
106 const uri = convertBskyAppUrlIfNeeded(f.url)
107 const [_0, did, _1, rkey] = uri.split('/').filter(Boolean)
108 const urip = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
109 return urip.toString()
110 }),
111 )
112}