Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 6bfe758d2a9ea376552fb45e5e589bccd0cf4df5 92 lines 3.3 kB view raw
1import {useMemo} from 'react' 2import { 3 type $Typed, 4 type AppBskyActorDefs, 5 AppBskyEmbedExternal, 6 AtUri, 7} from '@atproto/api' 8import {isAfter, parseISO} from 'date-fns' 9 10import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' 11import {type LiveNowConfig, useLiveNowConfig} from '#/state/service-config' 12import {useTickEveryMinute} from '#/state/shell' 13import type * as bsky from '#/types/bsky' 14 15export function useActorStatus(actor?: bsky.profile.AnyProfileView) { 16 const shadowed = useMaybeProfileShadow(actor) 17 const tick = useTickEveryMinute() 18 const config = useLiveNowConfig() 19 20 return useMemo(() => { 21 void tick // revalidate every minute 22 23 if (shadowed && 'status' in shadowed && shadowed.status) { 24 const isValid = isStatusValidForViewers(shadowed.status, config) 25 const isDisabled = shadowed.status.isDisabled || false 26 const isActive = isStatusStillActive(shadowed.status.expiresAt) 27 if (isValid && !isDisabled && isActive) { 28 return { 29 uri: shadowed.status.uri, 30 cid: shadowed.status.cid, 31 isDisabled: false, 32 isActive: true, 33 status: 'app.bsky.actor.status#live', 34 embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this 35 expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this 36 record: shadowed.status.record, 37 } satisfies AppBskyActorDefs.StatusView 38 } 39 return { 40 uri: shadowed.status.uri, 41 cid: shadowed.status.cid, 42 isDisabled, 43 isActive: false, 44 status: 'app.bsky.actor.status#live', 45 embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this 46 expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this 47 record: shadowed.status.record, 48 } satisfies AppBskyActorDefs.StatusView 49 } else { 50 return { 51 status: '', 52 isDisabled: false, 53 isActive: false, 54 record: {}, 55 } satisfies AppBskyActorDefs.StatusView 56 } 57 }, [shadowed, config, tick]) 58} 59 60export function isStatusStillActive(timeStr: string | undefined) { 61 if (!timeStr) return false 62 const now = new Date() 63 const expiry = parseISO(timeStr) 64 65 return isAfter(expiry, now) 66} 67 68/** 69 * Validates whether the live status is valid for display in the app. Does NOT 70 * validate if the status is valid for the acting user e.g. as they go live. 71 */ 72export function isStatusValidForViewers( 73 status: AppBskyActorDefs.StatusView, 74 config: LiveNowConfig, 75) { 76 if (status.status !== 'app.bsky.actor.status#live') return false 77 if (!status.uri) return false // should not happen, just backwards compat 78 try { 79 const {host: liveDid} = new AtUri(status.uri) 80 if (AppBskyEmbedExternal.isView(status.embed)) { 81 const url = new URL(status.embed.external.uri) 82 const exception = config.allowedHostsExceptionsByDid.get(liveDid) 83 const isValidException = exception ? exception.has(url.hostname) : false 84 const isValidForAnyone = config.defaultAllowedHosts.has(url.hostname) 85 return isValidException || isValidForAnyone 86 } else { 87 return false 88 } 89 } catch { 90 return false 91 } 92}