diff options
author | Samuel Newman <mozzius@protonmail.com> | 2025-05-10 00:06:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-10 00:06:06 +0300 |
commit | a0bd8042621e108f47e09dd096cf0d73fe1cee53 (patch) | |
tree | 0cc120c864ae8fea7f513ff242a1097ece0f1b8b /src/lib | |
parent | 2e80fa3dac4d869640f5bce8ad43eb401c8e3141 (diff) | |
download | voidsky-a0bd8042621e108f47e09dd096cf0d73fe1cee53.tar.zst |
Live (#8354)
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/actor-status.ts | 51 | ||||
-rw-r--r-- | src/lib/api/resolve.ts | 14 | ||||
-rw-r--r-- | src/lib/constants.ts | 1 | ||||
-rw-r--r-- | src/lib/strings/url-helpers.ts | 30 |
4 files changed, 89 insertions, 7 deletions
diff --git a/src/lib/actor-status.ts b/src/lib/actor-status.ts new file mode 100644 index 000000000..30921a88a --- /dev/null +++ b/src/lib/actor-status.ts @@ -0,0 +1,51 @@ +import {useMemo} from 'react' +import { + type $Typed, + type AppBskyActorDefs, + type AppBskyEmbedExternal, +} from '@atproto/api' +import {isAfter, parseISO} from 'date-fns' + +import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' +import {useTickEveryMinute} from '#/state/shell' +import {temp__canBeLive, temp__isStatusValid} from '#/components/live/temp' +import type * as bsky from '#/types/bsky' + +export function useActorStatus(actor?: bsky.profile.AnyProfileView) { + const shadowed = useMaybeProfileShadow(actor) + const tick = useTickEveryMinute() + return useMemo(() => { + tick! // revalidate every minute + + if ( + shadowed && + temp__canBeLive(shadowed) && + 'status' in shadowed && + shadowed.status && + temp__isStatusValid(shadowed.status) && + isStatusStillActive(shadowed.status.expiresAt) + ) { + return { + isActive: true, + status: 'app.bsky.actor.status#live', + embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this + expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this + record: shadowed.status.record, + } satisfies AppBskyActorDefs.StatusView + } else { + return { + status: '', + isActive: false, + record: {}, + } satisfies AppBskyActorDefs.StatusView + } + }, [shadowed, tick]) +} + +export function isStatusStillActive(timeStr: string | undefined) { + if (!timeStr) return false + const now = new Date() + const expiry = parseISO(timeStr) + + return isAfter(expiry, now) +} diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index 371062350..93d16ff0c 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,10 +1,10 @@ import { - AppBskyFeedDefs, - AppBskyGraphDefs, - ComAtprotoRepoStrongRef, + type AppBskyFeedDefs, + type AppBskyGraphDefs, + type ComAtprotoRepoStrongRef, } from '@atproto/api' import {AtUri} from '@atproto/api' -import {BskyAgent} from '@atproto/api' +import {type BskyAgent} from '@atproto/api' import {POST_IMG_MAX} from '#/lib/constants' import {getLinkMeta} from '#/lib/link-meta/link-meta' @@ -22,9 +22,9 @@ import { isBskyStartUrl, isShortLink, } from '#/lib/strings/url-helpers' -import {ComposerImage} from '#/state/gallery' +import {type ComposerImage} from '#/state/gallery' import {createComposerImage} from '#/state/gallery' -import {Gif} from '#/state/queries/tenor' +import {type Gif} from '#/state/queries/tenor' import {createGIFDescription} from '../gif-alt-text' import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers' @@ -213,7 +213,7 @@ async function resolveExternal( } } -async function imageToThumb( +export async function imageToThumb( imageUri: string, ): Promise<ComposerImage | undefined> { try { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index bb98f9fc8..dca03647a 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -31,6 +31,7 @@ export const DISCOVER_DEBUG_DIDS: Record<string, true> = { 'did:plc:3jpt2mvvsumj2r7eqk4gzzjz': true, // esb.lol 'did:plc:vjug55kidv6sye7ykr5faxxn': true, // emilyliu.me 'did:plc:tgqseeot47ymot4zro244fj3': true, // iwsmith.bsky.social + 'did:plc:2dzyut5lxna5ljiaasgeuffz': true, // mrnuma.bsky.social } const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new` diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index 20c3fabbc..ad194714a 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -372,3 +372,33 @@ export function getServiceAuthAudFromUrl(url: string | URL): string | null { } return `did:web:${hostname}` } + +// passes URL.parse, and has a TLD etc +export function definitelyUrl(maybeUrl: string) { + try { + if (maybeUrl.endsWith('.')) return null + + // Prepend 'https://' if the input doesn't start with a protocol + if (!maybeUrl.startsWith('https://') && !maybeUrl.startsWith('http://')) { + maybeUrl = 'https://' + maybeUrl + } + + const url = new URL(maybeUrl) + + // Extract the hostname and split it into labels + const hostname = url.hostname + const labels = hostname.split('.') + + // Ensure there are at least two labels (e.g., 'example' and 'com') + if (labels.length < 2) return null + + const tld = labels[labels.length - 1] + + // Check that the TLD is at least two characters long and contains only letters + if (!/^[a-z]{2,}$/i.test(tld)) return null + + return url.toString() + } catch { + return null + } +} |