forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useMemo} from 'react'
2import {
3 type $Typed,
4 type AppBskyActorDefs,
5 AppBskyEmbedExternal,
6} from '@atproto/api'
7import {isAfter, parseISO} from 'date-fns'
8
9import {useMaybeProfileShadow} from '#/state/cache/profile-shadow'
10import {type LiveNowConfig, useLiveNowConfig} from '#/state/service-config'
11import {useTickEveryMinute} from '#/state/shell'
12import type * as bsky from '#/types/bsky'
13
14export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
15 const shadowed = useMaybeProfileShadow(actor)
16 const tick = useTickEveryMinute()
17 const config = useLiveNowConfig()
18
19 return useMemo(() => {
20 void tick // revalidate every minute
21
22 if (shadowed && 'status' in shadowed && shadowed.status) {
23 const isValid = validateStatus(shadowed.status, config)
24 const isDisabled = shadowed.status.isDisabled || false
25 const isActive = isStatusStillActive(shadowed.status.expiresAt)
26 if (isValid && !isDisabled && isActive) {
27 return {
28 uri: shadowed.status.uri,
29 cid: shadowed.status.cid,
30 isDisabled: false,
31 isActive: true,
32 status: 'app.bsky.actor.status#live',
33 embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this
34 expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this
35 record: shadowed.status.record,
36 } satisfies AppBskyActorDefs.StatusView
37 }
38 return {
39 uri: shadowed.status.uri,
40 cid: shadowed.status.cid,
41 isDisabled,
42 isActive: false,
43 status: 'app.bsky.actor.status#live',
44 embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this
45 expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this
46 record: shadowed.status.record,
47 } satisfies AppBskyActorDefs.StatusView
48 } else {
49 return {
50 status: '',
51 isDisabled: false,
52 isActive: false,
53 record: {},
54 } satisfies AppBskyActorDefs.StatusView
55 }
56 }, [shadowed, config, tick])
57}
58
59export function isStatusStillActive(timeStr: string | undefined) {
60 if (!timeStr) return false
61 const now = new Date()
62 const expiry = parseISO(timeStr)
63
64 return isAfter(expiry, now)
65}
66
67export function validateStatus(
68 status: AppBskyActorDefs.StatusView,
69 config: LiveNowConfig,
70) {
71 if (status.status !== 'app.bsky.actor.status#live') return false
72 try {
73 if (AppBskyEmbedExternal.isView(status.embed)) {
74 const url = new URL(status.embed.external.uri)
75 return config.allowedDomains.has(url.hostname)
76 } else {
77 return false
78 }
79 } catch {
80 return false
81 }
82}