[APP-1750] Add the ability to report livestreams (#9654)

* Add report dialog to LiveStatus dialog

* Mr Worldwide™

* Bug fix bug fix

* Copy update

* Simplify parsing

* Bump api sdk

* update dev-env

* Update yarn.lock

* Bump api sdk

* Refactor useActorStatus, add disablement and appeal dialog

* Fix types

* Guard against chat profile views

* Go live (disabled)

* Fix types

* Status reports should only go to bsky

* Ah yeah let's not override types

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
Eric Bailey
2026-01-09 10:19:47 -06:00
committed by GitHub
parent 85a616eee0
commit edb19dd6cf
16 changed files with 325 additions and 55 deletions
@@ -70,6 +70,7 @@ export function useSubmitReportMutation() {
}
break
}
case 'status':
case 'post':
case 'list':
case 'feed':
@@ -3,6 +3,8 @@ import {
ToolsOzoneReportDefs as OzoneReportDefs,
} from '@atproto/api'
import {type ParsedReportSubject} from '#/components/moderation/ReportDialog/types'
export const DMCA_LINK = 'https://bsky.social/about/support/copyright'
export const SUPPORT_PAGE = 'https://bsky.social/about/support'
@@ -112,3 +114,10 @@ export const BSKY_LABELER_ONLY_REPORT_REASONS: Set<OzoneReportDefs.ReasonType> =
OzoneReportDefs.REASONCHILDSAFETYOTHER,
OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT,
])
/**
* Set of _parsed_ subject types that should only be sent to Bluesky's
* moderation service.
*/
export const BSKY_LABELER_ONLY_SUBJECT_TYPES: Set<ParsedReportSubject['type']> =
new Set(['convoMessage', 'status'])
@@ -14,6 +14,12 @@ export function useCopyForSubject(subject: ParsedReportSubject) {
subtitle: _(msg`Why should this user be reviewed?`),
}
}
case 'status': {
return {
title: _(msg`Report this livestream`),
subtitle: _(msg`Why should this livestream be reviewed?`),
}
}
case 'post': {
return {
title: _(msg`Report this post`),
@@ -16,6 +16,7 @@ import {atoms as a, useGutters, useTheme} from '#/alf'
import * as Admonition from '#/components/Admonition'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
import {useDelayedLoading} from '#/components/hooks/useDelayedLoading'
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotate'
import {
@@ -31,6 +32,7 @@ import {Text} from '#/components/Typography'
import {useSubmitReportMutation} from './action'
import {
BSKY_LABELER_ONLY_REPORT_REASONS,
BSKY_LABELER_ONLY_SUBJECT_TYPES,
NEW_TO_OLD_REASONS_MAP,
SUPPORT_PAGE,
} from './const'
@@ -44,17 +46,27 @@ import {
useReportOptions,
} from './utils/useReportOptions'
export {type ReportSubject} from './types'
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
export function useGlobalReportDialogControl() {
return useGlobalDialogsControlContext().reportDialogControl
}
const logger = Logger.create(Logger.Context.ReportDialog)
export function GlobalReportDialog() {
const {value, control} = useGlobalReportDialogControl()
return <ReportDialog control={control} subject={value?.subject} />
}
export function ReportDialog(
props: Omit<ReportDialogProps, 'subject'> & {
subject: ReportSubject
subject?: ReportSubject
},
) {
const subject = React.useMemo(
() => parseReportSubject(props.subject),
() => (props.subject ? parseReportSubject(props.subject) : undefined),
[props.subject],
)
const onClose = React.useCallback(() => {
@@ -116,8 +128,10 @@ function Inner(props: ReportDialogProps) {
const isBskyOnlyReason = state?.selectedOption?.reason
? BSKY_LABELER_ONLY_REPORT_REASONS.has(state.selectedOption.reason)
: false
// some subjects (chats) only go to Bluesky
const isBskyOnlySubject = props.subject.type === 'convoMessage'
// some subjects ONLY go to Bluesky
const isBskyOnlySubject = BSKY_LABELER_ONLY_SUBJECT_TYPES.has(
props.subject.type,
)
/**
* Labelers that support this `subject` and its NSID collection
@@ -824,12 +838,7 @@ function LabelerCard({
{title}
</Text>
<Text
style={[
a.text_sm,
,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}>
<Trans>By {sanitizeHandle(labeler.creator.handle, '@')}</Trans>
</Text>
</View>
@@ -18,6 +18,7 @@ export type ReportSubject =
| $Typed<AppBskyActorDefs.ProfileViewBasic>
| $Typed<AppBskyActorDefs.ProfileView>
| $Typed<AppBskyActorDefs.ProfileViewDetailed>
| $Typed<AppBskyActorDefs.StatusView>
| $Typed<AppBskyGraphDefs.ListView>
| $Typed<AppBskyFeedDefs.GeneratorView>
| $Typed<AppBskyGraphDefs.StarterPackView>
@@ -38,6 +39,12 @@ export type ParsedReportSubject =
quote: boolean
}
}
| {
type: 'status'
uri: string
cid: string
nsid: string
}
| {
type: 'list'
uri: string
@@ -33,6 +33,14 @@ export function parseReportSubject(
did: subject.did,
nsid: 'app.bsky.actor.profile',
}
} else if (AppBskyActorDefs.isStatusView(subject)) {
if (!subject.uri || !subject.cid) return
return {
type: 'status',
uri: subject.uri,
cid: subject.cid,
nsid: 'app.bsky.actor.status',
}
} else if (AppBskyGraphDefs.isListView(subject)) {
return {
type: 'list',