[Reporting] Add new reporting categories and reasons (#9079)
* start adding new report reasons * cleanup * use updated types * cleanup open/closed logic * other fix * state fix * rename type * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/index.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/components/moderation/ReportDialog/utils/useReportOptions.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * add mapping * Replace consts and maps using exported values * Map to deprecated reason if necessary, send some reports only to Bluesky * Use only new report reason types * Cleanup * Bump package, integrate new reasons * Adjust sorting, return type, integrate feedback * Just remove sort * Finish 'other' handling * Update descriptions * Just invert filter logic, no functional change but feels more correct * Clarify handling * fix deep imports * fix type error * [Reporting] Integrate new `ReportDialog` into DMs (#9213) * Update types, fork copy for convo vs message * Integrate in remaining spots * Replace DoneStep with separate dialog that opens after reporting * Handle profile error state by just showing a success message * Delete old dms ReportDialog * Chats only go to bsky * Only open block or delete dialog if submitted successfully * make close button always large * prevent expansion of blockordelete dialog * Delete old report dialog (#9214)e * Skip labeler selection for Bluesky-only cases * Rename AfterReportDialog --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Eric Bailey <git@esb.lol> Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import {useMutation} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {NEW_TO_OLD_REASONS_MAP} from './const'
|
||||
import {type ReportState} from './state'
|
||||
import {type ParsedReportSubject} from './types'
|
||||
|
||||
@@ -31,6 +32,26 @@ export function useSubmitReportMutation() {
|
||||
throw new Error(_(msg`Please select a moderation service`))
|
||||
}
|
||||
|
||||
const labeler = state.selectedLabeler
|
||||
const labelerSupportedReasonTypes = labeler.reasonTypes || []
|
||||
|
||||
let reasonType = state.selectedOption.reason
|
||||
const backwardsCompatibleReasonType = NEW_TO_OLD_REASONS_MAP[reasonType]
|
||||
const supportsNewReasonType =
|
||||
labelerSupportedReasonTypes.includes(reasonType)
|
||||
const supportsOldReasonType = labelerSupportedReasonTypes.includes(
|
||||
backwardsCompatibleReasonType,
|
||||
)
|
||||
|
||||
/*
|
||||
* Only fall back for backwards compatibility if the labeler
|
||||
* does not support the new reason type. If the labeler does not declare
|
||||
* supported reason types, send the new version.
|
||||
*/
|
||||
if (supportsOldReasonType && !supportsNewReasonType) {
|
||||
reasonType = backwardsCompatibleReasonType
|
||||
}
|
||||
|
||||
let report:
|
||||
| ComAtprotoModerationCreateReport.InputSchema
|
||||
| (Omit<ComAtprotoModerationCreateReport.InputSchema, 'subject'> & {
|
||||
@@ -40,7 +61,7 @@ export function useSubmitReportMutation() {
|
||||
switch (subject.type) {
|
||||
case 'account': {
|
||||
report = {
|
||||
reasonType: state.selectedOption.reason,
|
||||
reasonType,
|
||||
reason: state.details,
|
||||
subject: {
|
||||
$type: 'com.atproto.admin.defs#repoRef',
|
||||
@@ -54,7 +75,7 @@ export function useSubmitReportMutation() {
|
||||
case 'feed':
|
||||
case 'starterPack': {
|
||||
report = {
|
||||
reasonType: state.selectedOption.reason,
|
||||
reasonType,
|
||||
reason: state.details,
|
||||
subject: {
|
||||
$type: 'com.atproto.repo.strongRef',
|
||||
@@ -64,9 +85,9 @@ export function useSubmitReportMutation() {
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'chatMessage': {
|
||||
case 'convoMessage': {
|
||||
report = {
|
||||
reasonType: state.selectedOption.reason,
|
||||
reasonType,
|
||||
reason: state.details,
|
||||
subject: {
|
||||
$type: 'chat.bsky.convo.defs#messageRef',
|
||||
@@ -82,7 +103,7 @@ export function useSubmitReportMutation() {
|
||||
if (__DEV__) {
|
||||
logger.info('Submitting report', {
|
||||
labeler: {
|
||||
handle: state.selectedLabeler.creator.handle,
|
||||
handle: labeler.creator.handle,
|
||||
},
|
||||
report,
|
||||
})
|
||||
@@ -90,7 +111,7 @@ export function useSubmitReportMutation() {
|
||||
await agent.createModerationReport(report, {
|
||||
encoding: 'application/json',
|
||||
headers: {
|
||||
'atproto-proxy': `${state.selectedLabeler.creator.did}#atproto_labeler`,
|
||||
'atproto-proxy': `${labeler.creator.did}#atproto_labeler`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,2 +1,114 @@
|
||||
import {
|
||||
ComAtprotoModerationDefs as RootReportDefs,
|
||||
ToolsOzoneReportDefs as OzoneReportDefs,
|
||||
} from '@atproto/api'
|
||||
|
||||
export const DMCA_LINK = 'https://bsky.social/about/support/copyright'
|
||||
export const SUPPORT_PAGE = 'https://bsky.social/about/support'
|
||||
|
||||
export const NEW_TO_OLD_REASON_MAPPING: Record<string, string> = {}
|
||||
|
||||
/**
|
||||
* Mapping of new (Ozone namespace) reason types to old reason types.
|
||||
*
|
||||
* Matches the mapping defined in the Ozone codebase:
|
||||
* @see https://github.com/bluesky-social/atproto/blob/4c15fb47cec26060bff2e710e95869a90c9d7fdd/packages/ozone/src/mod-service/profile.ts#L16-L64
|
||||
*/
|
||||
export const NEW_TO_OLD_REASONS_MAP: Record<
|
||||
OzoneReportDefs.ReasonType,
|
||||
RootReportDefs.ReasonType
|
||||
> = {
|
||||
[OzoneReportDefs.REASONAPPEAL]: RootReportDefs.REASONAPPEAL,
|
||||
[OzoneReportDefs.REASONOTHER]: RootReportDefs.REASONOTHER,
|
||||
|
||||
[OzoneReportDefs.REASONVIOLENCEANIMAL]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONVIOLENCETHREATS]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONVIOLENCEGRAPHICCONTENT]:
|
||||
RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONVIOLENCEGLORIFICATION]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT]:
|
||||
RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONVIOLENCETRAFFICKING]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONVIOLENCEOTHER]: RootReportDefs.REASONVIOLATION,
|
||||
|
||||
[OzoneReportDefs.REASONSEXUALABUSECONTENT]: RootReportDefs.REASONSEXUAL,
|
||||
[OzoneReportDefs.REASONSEXUALNCII]: RootReportDefs.REASONSEXUAL,
|
||||
[OzoneReportDefs.REASONSEXUALDEEPFAKE]: RootReportDefs.REASONSEXUAL,
|
||||
[OzoneReportDefs.REASONSEXUALANIMAL]: RootReportDefs.REASONSEXUAL,
|
||||
[OzoneReportDefs.REASONSEXUALUNLABELED]: RootReportDefs.REASONSEXUAL,
|
||||
[OzoneReportDefs.REASONSEXUALOTHER]: RootReportDefs.REASONSEXUAL,
|
||||
|
||||
[OzoneReportDefs.REASONCHILDSAFETYCSAM]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONCHILDSAFETYGROOM]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONCHILDSAFETYPRIVACY]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONCHILDSAFETYHARASSMENT]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONCHILDSAFETYOTHER]: RootReportDefs.REASONVIOLATION,
|
||||
|
||||
[OzoneReportDefs.REASONHARASSMENTTROLL]: RootReportDefs.REASONRUDE,
|
||||
[OzoneReportDefs.REASONHARASSMENTTARGETED]: RootReportDefs.REASONRUDE,
|
||||
[OzoneReportDefs.REASONHARASSMENTHATESPEECH]: RootReportDefs.REASONRUDE,
|
||||
[OzoneReportDefs.REASONHARASSMENTDOXXING]: RootReportDefs.REASONRUDE,
|
||||
[OzoneReportDefs.REASONHARASSMENTOTHER]: RootReportDefs.REASONRUDE,
|
||||
|
||||
[OzoneReportDefs.REASONMISLEADINGBOT]: RootReportDefs.REASONMISLEADING,
|
||||
[OzoneReportDefs.REASONMISLEADINGIMPERSONATION]:
|
||||
RootReportDefs.REASONMISLEADING,
|
||||
[OzoneReportDefs.REASONMISLEADINGSPAM]: RootReportDefs.REASONSPAM,
|
||||
[OzoneReportDefs.REASONMISLEADINGSCAM]: RootReportDefs.REASONMISLEADING,
|
||||
[OzoneReportDefs.REASONMISLEADINGELECTIONS]: RootReportDefs.REASONMISLEADING,
|
||||
[OzoneReportDefs.REASONMISLEADINGOTHER]: RootReportDefs.REASONMISLEADING,
|
||||
|
||||
[OzoneReportDefs.REASONRULESITESECURITY]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONRULEPROHIBITEDSALES]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONRULEBANEVASION]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONRULEOTHER]: RootReportDefs.REASONVIOLATION,
|
||||
|
||||
[OzoneReportDefs.REASONSELFHARMCONTENT]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONSELFHARMED]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONSELFHARMSTUNTS]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONSELFHARMSUBSTANCES]: RootReportDefs.REASONVIOLATION,
|
||||
[OzoneReportDefs.REASONSELFHARMOTHER]: RootReportDefs.REASONVIOLATION,
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping of old reason types to new (Ozone namespace) reason types.
|
||||
* @see https://github.com/bluesky-social/proposals/tree/main/0009-mod-report-granularity#backwards-compatibility
|
||||
*/
|
||||
export const OLD_TO_NEW_REASONS_MAP: Record<
|
||||
Exclude<RootReportDefs.ReasonType, OzoneReportDefs.ReasonType>,
|
||||
OzoneReportDefs.ReasonType
|
||||
> = {
|
||||
[RootReportDefs.REASONSPAM]: [OzoneReportDefs.REASONMISLEADINGSPAM],
|
||||
[RootReportDefs.REASONVIOLATION]: [OzoneReportDefs.REASONRULEOTHER],
|
||||
[RootReportDefs.REASONMISLEADING]: [OzoneReportDefs.REASONMISLEADINGOTHER],
|
||||
[RootReportDefs.REASONSEXUAL]: [OzoneReportDefs.REASONSEXUALUNLABELED],
|
||||
[RootReportDefs.REASONRUDE]: [OzoneReportDefs.REASONHARASSMENTOTHER],
|
||||
[RootReportDefs.REASONOTHER]: [OzoneReportDefs.REASONOTHER],
|
||||
[RootReportDefs.REASONAPPEAL]: [OzoneReportDefs.REASONAPPEAL],
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of report reasons that should optionally include additional details from
|
||||
* the reporter.
|
||||
*/
|
||||
export const OTHER_REPORT_REASONS: Set<OzoneReportDefs.ReasonType> = new Set([
|
||||
OzoneReportDefs.REASONVIOLENCEOTHER,
|
||||
OzoneReportDefs.REASONSEXUALOTHER,
|
||||
OzoneReportDefs.REASONCHILDSAFETYOTHER,
|
||||
OzoneReportDefs.REASONHARASSMENTOTHER,
|
||||
OzoneReportDefs.REASONMISLEADINGOTHER,
|
||||
OzoneReportDefs.REASONRULEOTHER,
|
||||
OzoneReportDefs.REASONSELFHARMOTHER,
|
||||
OzoneReportDefs.REASONOTHER,
|
||||
])
|
||||
|
||||
/**
|
||||
* Set of report reasons that should only be sent to Bluesky's moderation service.
|
||||
*/
|
||||
export const BSKY_LABELER_ONLY_REPORT_REASONS: Set<OzoneReportDefs.ReasonType> =
|
||||
new Set([
|
||||
OzoneReportDefs.REASONCHILDSAFETYCSAM,
|
||||
OzoneReportDefs.REASONCHILDSAFETYGROOM,
|
||||
OzoneReportDefs.REASONCHILDSAFETYOTHER,
|
||||
OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT,
|
||||
])
|
||||
|
||||
@@ -38,10 +38,20 @@ export function useCopyForSubject(subject: ParsedReportSubject) {
|
||||
subtitle: _(msg`Why should this starter pack be reviewed?`),
|
||||
}
|
||||
}
|
||||
case 'chatMessage': {
|
||||
return {
|
||||
title: _(msg`Report this message`),
|
||||
subtitle: _(msg`Why should this message be reviewed?`),
|
||||
case 'convoMessage': {
|
||||
switch (subject.view) {
|
||||
case 'convo': {
|
||||
return {
|
||||
title: _(msg`Report this conversation`),
|
||||
subtitle: _(msg`Why should this conversation be reviewed?`),
|
||||
}
|
||||
}
|
||||
case 'message': {
|
||||
return {
|
||||
title: _(msg`Report this message`),
|
||||
subtitle: _(msg`Why should this message be reviewed?`),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {type ScrollView} from 'react-native-gesture-handler'
|
||||
import {type AppBskyLabelerDefs} from '@atproto/api'
|
||||
import {type AppBskyLabelerDefs, BSKY_LABELER_DID} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
@@ -30,12 +30,20 @@ import {createStaticClick, InlineLinkText, Link} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useSubmitReportMutation} from './action'
|
||||
import {SUPPORT_PAGE} from './const'
|
||||
import {
|
||||
BSKY_LABELER_ONLY_REPORT_REASONS,
|
||||
NEW_TO_OLD_REASONS_MAP,
|
||||
SUPPORT_PAGE,
|
||||
} from './const'
|
||||
import {useCopyForSubject} from './copy'
|
||||
import {initialState, reducer} from './state'
|
||||
import {type ReportDialogProps, type ReportSubject} from './types'
|
||||
import {parseReportSubject} from './utils/parseReportSubject'
|
||||
import {type ReportOption, useReportOptions} from './utils/useReportOptions'
|
||||
import {
|
||||
type ReportCategoryConfig,
|
||||
type ReportOption,
|
||||
useReportOptions,
|
||||
} from './utils/useReportOptions'
|
||||
|
||||
export {useDialogControl as useReportDialogControl} from '#/components/Dialog'
|
||||
|
||||
@@ -95,7 +103,7 @@ function Inner(props: ReportDialogProps) {
|
||||
} = useMyLabelersQuery({excludeNonConfigurableLabelers: true})
|
||||
const isLoading = useDelayedLoading(500, isLabelerLoading)
|
||||
const copy = useCopyForSubject(props.subject)
|
||||
const reportOptions = useReportOptions()
|
||||
const {categories, getCategory} = useReportOptions()
|
||||
const [state, dispatch] = React.useReducer(reducer, initialState)
|
||||
|
||||
/**
|
||||
@@ -105,6 +113,13 @@ function Inner(props: ReportDialogProps) {
|
||||
const [isPending, setPending] = React.useState(false)
|
||||
const [isSuccess, setSuccess] = React.useState(false)
|
||||
|
||||
// some reasons ONLY go to Bluesky
|
||||
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'
|
||||
|
||||
/**
|
||||
* Labelers that support this `subject` and its NSID collection
|
||||
*/
|
||||
@@ -116,7 +131,7 @@ function Inner(props: ReportDialogProps) {
|
||||
if (subjectTypes === undefined) return true
|
||||
if (props.subject.type === 'account') {
|
||||
return subjectTypes.includes('account')
|
||||
} else if (props.subject.type === 'chatMessage') {
|
||||
} else if (props.subject.type === 'convoMessage') {
|
||||
return subjectTypes.includes('chat')
|
||||
} else {
|
||||
return subjectTypes.includes('record')
|
||||
@@ -126,19 +141,44 @@ function Inner(props: ReportDialogProps) {
|
||||
const collections: string[] | undefined = l.subjectCollections
|
||||
if (collections === undefined) return true
|
||||
// all chat collections accepted, since only Bluesky handles chats
|
||||
if (props.subject.type === 'chatMessage') return true
|
||||
if (props.subject.type === 'convoMessage') return true
|
||||
return collections.includes(props.subject.nsid)
|
||||
})
|
||||
.filter(l => {
|
||||
if (!state.selectedOption) return true
|
||||
const reasonTypes: string[] | undefined = l.reasonTypes
|
||||
if (reasonTypes === undefined) return true
|
||||
return reasonTypes.includes(state.selectedOption.reason)
|
||||
if (!state.selectedOption) return false
|
||||
if (isBskyOnlyReason || isBskyOnlySubject) {
|
||||
return l.creator.did === BSKY_LABELER_DID
|
||||
}
|
||||
const supportedReasonTypes: string[] | undefined = l.reasonTypes
|
||||
if (supportedReasonTypes === undefined) return true
|
||||
return (
|
||||
// supports new reason type
|
||||
supportedReasonTypes.includes(state.selectedOption.reason) ||
|
||||
// supports old reason type (backwards compat)
|
||||
supportedReasonTypes.includes(
|
||||
NEW_TO_OLD_REASONS_MAP[state.selectedOption.reason],
|
||||
)
|
||||
)
|
||||
})
|
||||
}, [props, allLabelers, state.selectedOption])
|
||||
}, [
|
||||
props,
|
||||
allLabelers,
|
||||
state.selectedOption,
|
||||
isBskyOnlyReason,
|
||||
isBskyOnlySubject,
|
||||
])
|
||||
const hasSupportedLabelers = !!supportedLabelers.length
|
||||
const hasSingleSupportedLabeler = supportedLabelers.length === 1
|
||||
|
||||
/**
|
||||
* We skip the select labeler step if there's only one possible labeler, and
|
||||
* that labeler is Bluesky (which is the case for chat reports and certain
|
||||
* reason types). We'll use this below to adjust the indexing and skip the
|
||||
* step in the UI.
|
||||
*/
|
||||
const isAlwaysBskyLabeler =
|
||||
hasSingleSupportedLabeler && (isBskyOnlyReason || isBskyOnlySubject)
|
||||
|
||||
const onSubmit = React.useCallback(async () => {
|
||||
dispatch({type: 'clearError'})
|
||||
|
||||
@@ -166,7 +206,9 @@ function Inner(props: ReportDialogProps) {
|
||||
)
|
||||
// give time for user feedback
|
||||
setTimeout(() => {
|
||||
props.control.close()
|
||||
props.control.close(() => {
|
||||
props.onAfterSubmit?.()
|
||||
})
|
||||
}, 1e3)
|
||||
} catch (e: any) {
|
||||
logger.metric('reportDialog:failure', {}, {statsig: false})
|
||||
@@ -237,32 +279,36 @@ function Inner(props: ReportDialogProps) {
|
||||
</Admonition.Outer>
|
||||
) : (
|
||||
<>
|
||||
{state.selectedOption ? (
|
||||
{state.selectedCategory ? (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<OptionCard option={state.selectedOption} />
|
||||
<CategoryCard option={state.selectedCategory} />
|
||||
</View>
|
||||
<Button
|
||||
testID="report:clearOption"
|
||||
label={_(msg`Change report reason`)}
|
||||
testID="report:clearCategory"
|
||||
label={_(msg`Change report category`)}
|
||||
size="tiny"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
onPress={() => {
|
||||
dispatch({type: 'clearOption'})
|
||||
dispatch({type: 'clearCategory'})
|
||||
}}>
|
||||
<ButtonIcon icon={X} />
|
||||
</Button>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[a.gap_sm]}>
|
||||
{reportOptions[props.subject.type].map(o => (
|
||||
<OptionCard
|
||||
key={o.reason}
|
||||
{categories.map(o => (
|
||||
<CategoryCard
|
||||
key={o.key}
|
||||
option={o}
|
||||
onSelect={() => {
|
||||
dispatch({type: 'selectOption', option: o})
|
||||
dispatch({
|
||||
type: 'selectCategory',
|
||||
option: o,
|
||||
otherOption: getCategory('other').options[0],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -310,87 +356,142 @@ function Inner(props: ReportDialogProps) {
|
||||
<StepOuter>
|
||||
<StepTitle
|
||||
index={2}
|
||||
title={_(msg`Select moderation service`)}
|
||||
title={_(msg`Select a reason`)}
|
||||
activeIndex1={state.activeStepIndex1}
|
||||
/>
|
||||
{state.activeStepIndex1 >= 2 && (
|
||||
<>
|
||||
{state.selectedLabeler ? (
|
||||
<>
|
||||
{hasSingleSupportedLabeler ? (
|
||||
<LabelerCard labeler={state.selectedLabeler} />
|
||||
) : (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<LabelerCard labeler={state.selectedLabeler} />
|
||||
{state.selectedOption ? (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<OptionCard option={state.selectedOption} />
|
||||
</View>
|
||||
<Button
|
||||
testID="report:clearReportOption"
|
||||
label={_(msg`Change report reason`)}
|
||||
size="tiny"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
onPress={() => {
|
||||
dispatch({type: 'clearOption'})
|
||||
}}>
|
||||
<ButtonIcon icon={X} />
|
||||
</Button>
|
||||
</View>
|
||||
) : state.selectedCategory ? (
|
||||
<View style={[a.gap_sm]}>
|
||||
{getCategory(state.selectedCategory.key).options.map(o => (
|
||||
<OptionCard
|
||||
key={o.reason}
|
||||
option={o}
|
||||
onSelect={() => {
|
||||
dispatch({type: 'selectOption', option: o})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
</StepOuter>
|
||||
|
||||
{isAlwaysBskyLabeler ? (
|
||||
<ActionOnce
|
||||
check={() => !state.selectedLabeler}
|
||||
callback={() => {
|
||||
dispatch({
|
||||
type: 'selectLabeler',
|
||||
labeler: supportedLabelers[0],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<StepOuter>
|
||||
<StepTitle
|
||||
index={3}
|
||||
title={_(msg`Select moderation service`)}
|
||||
activeIndex1={state.activeStepIndex1}
|
||||
/>
|
||||
{state.activeStepIndex1 >= 3 && (
|
||||
<>
|
||||
{state.selectedLabeler ? (
|
||||
<>
|
||||
{hasSingleSupportedLabeler ? (
|
||||
<LabelerCard labeler={state.selectedLabeler} />
|
||||
) : (
|
||||
<View style={[a.flex_row, a.align_center, a.gap_md]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<LabelerCard labeler={state.selectedLabeler} />
|
||||
</View>
|
||||
<Button
|
||||
label={_(msg`Change moderation service`)}
|
||||
size="tiny"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
onPress={() => {
|
||||
dispatch({type: 'clearLabeler'})
|
||||
}}>
|
||||
<ButtonIcon icon={X} />
|
||||
</Button>
|
||||
</View>
|
||||
<Button
|
||||
label={_(msg`Change moderation service`)}
|
||||
size="tiny"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
onPress={() => {
|
||||
dispatch({type: 'clearLabeler'})
|
||||
}}>
|
||||
<ButtonIcon icon={X} />
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{hasSupportedLabelers ? (
|
||||
<View style={[a.gap_sm]}>
|
||||
{hasSingleSupportedLabeler ? (
|
||||
<>
|
||||
<LabelerCard labeler={supportedLabelers[0]} />
|
||||
<ActionOnce
|
||||
check={() => !state.selectedLabeler}
|
||||
callback={() => {
|
||||
dispatch({
|
||||
type: 'selectLabeler',
|
||||
labeler: supportedLabelers[0],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{supportedLabelers.map(l => (
|
||||
<LabelerCard
|
||||
key={l.creator.did}
|
||||
labeler={l}
|
||||
onSelect={() => {
|
||||
dispatch({type: 'selectLabeler', labeler: l})
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{hasSupportedLabelers ? (
|
||||
<View style={[a.gap_sm]}>
|
||||
{hasSingleSupportedLabeler ? (
|
||||
<>
|
||||
<LabelerCard labeler={supportedLabelers[0]} />
|
||||
<ActionOnce
|
||||
check={() => !state.selectedLabeler}
|
||||
callback={() => {
|
||||
dispatch({
|
||||
type: 'selectLabeler',
|
||||
labeler: supportedLabelers[0],
|
||||
})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
// should never happen in our app
|
||||
<Admonition.Admonition type="warning">
|
||||
<Trans>
|
||||
Unfortunately, none of your subscribed labelers supports
|
||||
this report type.
|
||||
</Trans>
|
||||
</Admonition.Admonition>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StepOuter>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{supportedLabelers.map(l => (
|
||||
<LabelerCard
|
||||
key={l.creator.did}
|
||||
labeler={l}
|
||||
onSelect={() => {
|
||||
dispatch({type: 'selectLabeler', labeler: l})
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
// should never happen in our app
|
||||
<Admonition.Admonition type="warning">
|
||||
<Trans>
|
||||
Unfortunately, none of your subscribed labelers
|
||||
supports this report type.
|
||||
</Trans>
|
||||
</Admonition.Admonition>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StepOuter>
|
||||
)}
|
||||
|
||||
<StepOuter>
|
||||
<StepTitle
|
||||
index={3}
|
||||
index={isAlwaysBskyLabeler ? 3 : 4}
|
||||
title={_(msg`Submit report`)}
|
||||
activeIndex1={state.activeStepIndex1}
|
||||
activeIndex1={
|
||||
isAlwaysBskyLabeler
|
||||
? state.activeStepIndex1 - 1
|
||||
: state.activeStepIndex1
|
||||
}
|
||||
/>
|
||||
{state.activeStepIndex1 === 3 && (
|
||||
{state.activeStepIndex1 === 4 && (
|
||||
<>
|
||||
<View style={[a.pb_xs, a.gap_xs]}>
|
||||
<Text style={[a.leading_snug, a.pb_xs]}>
|
||||
@@ -569,12 +670,12 @@ function StepTitle({
|
||||
)
|
||||
}
|
||||
|
||||
function OptionCard({
|
||||
function CategoryCard({
|
||||
option,
|
||||
onSelect,
|
||||
}: {
|
||||
option: ReportOption
|
||||
onSelect?: (option: ReportOption) => void
|
||||
option: ReportCategoryConfig
|
||||
onSelect?: (option: ReportCategoryConfig) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
@@ -584,7 +685,7 @@ function OptionCard({
|
||||
}, [onSelect, option])
|
||||
return (
|
||||
<Button
|
||||
testID={`report:option:${option.reason}`}
|
||||
testID={`report:option:${option.title}`}
|
||||
label={_(msg`Create report for ${option.title}`)}
|
||||
onPress={onPress}
|
||||
disabled={!onSelect}>
|
||||
@@ -614,6 +715,53 @@ function OptionCard({
|
||||
)
|
||||
}
|
||||
|
||||
function OptionCard({
|
||||
option,
|
||||
onSelect,
|
||||
}: {
|
||||
option: ReportOption
|
||||
onSelect?: (option: ReportOption) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const gutters = useGutters(['compact'])
|
||||
const onPress = React.useCallback(() => {
|
||||
onSelect?.(option)
|
||||
}, [onSelect, option])
|
||||
return (
|
||||
<Button
|
||||
testID={`report:option:${option.title}`}
|
||||
label={_(
|
||||
msg({
|
||||
message: `Create report for ${option.title}`,
|
||||
comment:
|
||||
'Accessibility label for button to create a moderation report for the selected option',
|
||||
}),
|
||||
)}
|
||||
onPress={onPress}
|
||||
disabled={!onSelect}>
|
||||
{({hovered, pressed}) => (
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
gutters,
|
||||
a.py_sm,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.bg_contrast_25,
|
||||
hovered || pressed
|
||||
? [t.atoms.border_contrast_high]
|
||||
: [t.atoms.border_contrast_low],
|
||||
]}>
|
||||
<Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
|
||||
{option.title}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function OptionCardSkeleton() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import {type AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api'
|
||||
import {type AppBskyLabelerDefs} from '@atproto/api'
|
||||
|
||||
import {type ReportOption} from './utils/useReportOptions'
|
||||
import {OTHER_REPORT_REASONS} from '#/components/moderation/ReportDialog/const'
|
||||
import {
|
||||
type ReportCategoryConfig,
|
||||
type ReportOption,
|
||||
} from '#/components/moderation/ReportDialog/utils/useReportOptions'
|
||||
|
||||
export type ReportState = {
|
||||
selectedCategory?: ReportCategoryConfig
|
||||
selectedOption?: ReportOption
|
||||
selectedLabeler?: AppBskyLabelerDefs.LabelerViewDetailed
|
||||
details?: string
|
||||
@@ -12,6 +17,14 @@ export type ReportState = {
|
||||
}
|
||||
|
||||
export type ReportAction =
|
||||
| {
|
||||
type: 'selectCategory'
|
||||
option: ReportCategoryConfig
|
||||
otherOption: ReportOption
|
||||
}
|
||||
| {
|
||||
type: 'clearCategory'
|
||||
}
|
||||
| {
|
||||
type: 'selectOption'
|
||||
option: ReportOption
|
||||
@@ -42,6 +55,7 @@ export type ReportAction =
|
||||
}
|
||||
|
||||
export const initialState: ReportState = {
|
||||
selectedCategory: undefined,
|
||||
selectedOption: undefined,
|
||||
selectedLabeler: undefined,
|
||||
details: undefined,
|
||||
@@ -51,39 +65,52 @@ export const initialState: ReportState = {
|
||||
|
||||
export function reducer(state: ReportState, action: ReportAction): ReportState {
|
||||
switch (action.type) {
|
||||
case 'selectCategory':
|
||||
return {
|
||||
...state,
|
||||
selectedCategory: action.option,
|
||||
activeStepIndex1: action.option.key === 'other' ? 3 : 2,
|
||||
selectedOption:
|
||||
action.option.key === 'other' ? action.otherOption : undefined,
|
||||
}
|
||||
case 'clearCategory':
|
||||
return {
|
||||
...state,
|
||||
selectedCategory: undefined,
|
||||
selectedOption: undefined,
|
||||
selectedLabeler: undefined,
|
||||
activeStepIndex1: 1,
|
||||
detailsOpen: false,
|
||||
}
|
||||
case 'selectOption':
|
||||
return {
|
||||
...state,
|
||||
selectedOption: action.option,
|
||||
activeStepIndex1: 2,
|
||||
detailsOpen:
|
||||
!!state.details ||
|
||||
action.option.reason === ComAtprotoModerationDefs.REASONOTHER,
|
||||
activeStepIndex1: 3,
|
||||
detailsOpen: OTHER_REPORT_REASONS.has(action.option.reason),
|
||||
}
|
||||
case 'clearOption':
|
||||
return {
|
||||
...state,
|
||||
selectedOption: undefined,
|
||||
selectedLabeler: undefined,
|
||||
activeStepIndex1: 1,
|
||||
detailsOpen:
|
||||
!!state.details ||
|
||||
state.selectedOption?.reason === ComAtprotoModerationDefs.REASONOTHER,
|
||||
activeStepIndex1: 2,
|
||||
detailsOpen: false,
|
||||
}
|
||||
case 'selectLabeler':
|
||||
return {
|
||||
...state,
|
||||
selectedLabeler: action.labeler,
|
||||
activeStepIndex1: 3,
|
||||
activeStepIndex1: 4,
|
||||
detailsOpen: state.selectedOption
|
||||
? OTHER_REPORT_REASONS.has(state.selectedOption?.reason)
|
||||
: false,
|
||||
}
|
||||
case 'clearLabeler':
|
||||
return {
|
||||
...state,
|
||||
selectedLabeler: undefined,
|
||||
activeStepIndex1: 2,
|
||||
detailsOpen:
|
||||
!!state.details ||
|
||||
state.selectedOption?.reason === ComAtprotoModerationDefs.REASONOTHER,
|
||||
activeStepIndex1: 3,
|
||||
}
|
||||
case 'setDetails':
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,12 @@ import {
|
||||
|
||||
import type * as Dialog from '#/components/Dialog'
|
||||
|
||||
export type ReportSubjectConvo = {
|
||||
view: 'convo' | 'message'
|
||||
convoId: string
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
}
|
||||
|
||||
export type ReportSubject =
|
||||
| $Typed<AppBskyActorDefs.ProfileViewBasic>
|
||||
| $Typed<AppBskyActorDefs.ProfileView>
|
||||
@@ -16,7 +22,7 @@ export type ReportSubject =
|
||||
| $Typed<AppBskyFeedDefs.GeneratorView>
|
||||
| $Typed<AppBskyGraphDefs.StarterPackView>
|
||||
| $Typed<AppBskyFeedDefs.PostView>
|
||||
| {convoId: string; message: ChatBskyConvoDefs.MessageView}
|
||||
| ReportSubjectConvo
|
||||
|
||||
export type ParsedReportSubject =
|
||||
| {
|
||||
@@ -55,13 +61,15 @@ export type ParsedReportSubject =
|
||||
did: string
|
||||
nsid: string
|
||||
}
|
||||
| {
|
||||
type: 'chatMessage'
|
||||
convoId: string
|
||||
message: ChatBskyConvoDefs.MessageView
|
||||
}
|
||||
| ({
|
||||
type: 'convoMessage'
|
||||
} & ReportSubjectConvo)
|
||||
|
||||
export type ReportDialogProps = {
|
||||
control: Dialog.DialogOuterProps['control']
|
||||
subject: ParsedReportSubject
|
||||
/**
|
||||
* Called if the report was successfully submitted.
|
||||
*/
|
||||
onAfterSubmit?: () => void
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export function parseReportSubject(
|
||||
|
||||
if ('convoId' in subject) {
|
||||
return {
|
||||
type: 'chatMessage',
|
||||
type: 'convoMessage',
|
||||
...subject,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +1,256 @@
|
||||
import {useMemo} from 'react'
|
||||
import {ComAtprotoModerationDefs} from '@atproto/api'
|
||||
import {ToolsOzoneReportDefs as OzoneReportDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
export interface ReportOption {
|
||||
reason: string
|
||||
export type ReportCategory =
|
||||
| 'childSafety'
|
||||
| 'violencePhysicalHarm'
|
||||
| 'sexualAdultContent'
|
||||
| 'harassmentHate'
|
||||
| 'misleading'
|
||||
| 'ruleBreaking'
|
||||
| 'selfHarm'
|
||||
| 'other'
|
||||
|
||||
export type ReportCategoryConfig = {
|
||||
key: ReportCategory
|
||||
title: string
|
||||
description: string
|
||||
options: ReportOption[]
|
||||
}
|
||||
|
||||
interface ReportOptions {
|
||||
account: ReportOption[]
|
||||
post: ReportOption[]
|
||||
list: ReportOption[]
|
||||
starterPack: ReportOption[]
|
||||
feed: ReportOption[]
|
||||
chatMessage: ReportOption[]
|
||||
export type ReportOption = {
|
||||
title: string
|
||||
reason: OzoneReportDefs.ReasonType
|
||||
}
|
||||
|
||||
export function useReportOptions(): ReportOptions {
|
||||
export function useReportOptions() {
|
||||
const {_} = useLingui()
|
||||
|
||||
return useMemo(() => {
|
||||
const other = {
|
||||
reason: ComAtprotoModerationDefs.REASONOTHER,
|
||||
title: _(msg`Other`),
|
||||
description: _(msg`An issue not included in these options`),
|
||||
const categories: Record<ReportCategory, ReportCategoryConfig> = {
|
||||
misleading: {
|
||||
key: 'misleading',
|
||||
title: _(msg`Misleading`),
|
||||
description: _(msg`Spam or other inauthentic behavior or deception`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Spam`),
|
||||
reason: OzoneReportDefs.REASONMISLEADINGSPAM,
|
||||
},
|
||||
{
|
||||
title: _(msg`Scam`),
|
||||
reason: OzoneReportDefs.REASONMISLEADINGSCAM,
|
||||
},
|
||||
{
|
||||
title: _(msg`Fake account or bot`),
|
||||
reason: OzoneReportDefs.REASONMISLEADINGBOT,
|
||||
},
|
||||
{
|
||||
title: _(msg`Impersonation`),
|
||||
reason: OzoneReportDefs.REASONMISLEADINGIMPERSONATION,
|
||||
},
|
||||
{
|
||||
title: _(msg`False information about elections`),
|
||||
reason: OzoneReportDefs.REASONMISLEADINGELECTIONS,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other misleading content`),
|
||||
reason: OzoneReportDefs.REASONMISLEADINGOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
sexualAdultContent: {
|
||||
key: 'sexualAdultContent',
|
||||
title: _(msg`Adult content`),
|
||||
description: _(
|
||||
msg`Unlabeled, abusive, or non-consensual adult content`,
|
||||
),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Unlabeled adult content`),
|
||||
reason: OzoneReportDefs.REASONSEXUALUNLABELED,
|
||||
},
|
||||
{
|
||||
title: _(msg`Adult sexual abuse content`),
|
||||
reason: OzoneReportDefs.REASONSEXUALABUSECONTENT,
|
||||
},
|
||||
{
|
||||
title: _(msg`Non-consensual intimate imagery`),
|
||||
reason: OzoneReportDefs.REASONSEXUALNCII,
|
||||
},
|
||||
{
|
||||
title: _(msg`Deepfake adult content`),
|
||||
reason: OzoneReportDefs.REASONSEXUALDEEPFAKE,
|
||||
},
|
||||
{
|
||||
title: _(msg`Animal sexual abuse`),
|
||||
reason: OzoneReportDefs.REASONSEXUALANIMAL,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other sexual violence content`),
|
||||
reason: OzoneReportDefs.REASONSEXUALOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
harassmentHate: {
|
||||
key: 'harassmentHate',
|
||||
title: _(msg`Harassment or hate`),
|
||||
description: _(msg`Abusive or discriminatory behavior`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Trolling`),
|
||||
reason: OzoneReportDefs.REASONHARASSMENTTROLL,
|
||||
},
|
||||
{
|
||||
title: _(msg`Targeted harassment`),
|
||||
reason: OzoneReportDefs.REASONHARASSMENTTARGETED,
|
||||
},
|
||||
{
|
||||
title: _(msg`Hate speech`),
|
||||
reason: OzoneReportDefs.REASONHARASSMENTHATESPEECH,
|
||||
},
|
||||
{
|
||||
title: _(msg`Doxxing`),
|
||||
reason: OzoneReportDefs.REASONHARASSMENTDOXXING,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other harassing or hateful content`),
|
||||
reason: OzoneReportDefs.REASONHARASSMENTOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
violencePhysicalHarm: {
|
||||
key: 'violencePhysicalHarm',
|
||||
title: _(msg`Violence`),
|
||||
description: _(msg`Violent or threatening content`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Animal welfare`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCEANIMAL,
|
||||
},
|
||||
{
|
||||
title: _(msg`Threats or incitement`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCETHREATS,
|
||||
},
|
||||
{
|
||||
title: _(msg`Graphic violent content`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCEGRAPHICCONTENT,
|
||||
},
|
||||
{
|
||||
title: _(msg`Glorification of violence`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCEGLORIFICATION,
|
||||
},
|
||||
{
|
||||
title: _(msg`Extremist content`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT,
|
||||
},
|
||||
{
|
||||
title: _(msg`Human trafficking`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCETRAFFICKING,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other violent content`),
|
||||
reason: OzoneReportDefs.REASONVIOLENCEOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
childSafety: {
|
||||
key: 'childSafety',
|
||||
title: _(msg`Child safety`),
|
||||
description: _(msg`Harming or endangering minors`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Child Sexual Abuse Material (CSAM)`),
|
||||
reason: OzoneReportDefs.REASONCHILDSAFETYCSAM,
|
||||
},
|
||||
{
|
||||
title: _(msg`Grooming or predatory behavior`),
|
||||
reason: OzoneReportDefs.REASONCHILDSAFETYGROOM,
|
||||
},
|
||||
{
|
||||
title: _(msg`Privacy violation of a minor`),
|
||||
reason: OzoneReportDefs.REASONCHILDSAFETYPRIVACY,
|
||||
},
|
||||
{
|
||||
title: _(msg`Minor harassment or bullying`),
|
||||
reason: OzoneReportDefs.REASONCHILDSAFETYHARASSMENT,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other child safety issue`),
|
||||
reason: OzoneReportDefs.REASONCHILDSAFETYOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
selfHarm: {
|
||||
key: 'selfHarm',
|
||||
title: _(msg`Self-harm or dangerous behaviors`),
|
||||
description: _(msg`Harmful or high-risk activities`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Content promoting or depicting self-harm`),
|
||||
reason: OzoneReportDefs.REASONSELFHARMCONTENT,
|
||||
},
|
||||
{
|
||||
title: _(msg`Eating disorders`),
|
||||
reason: OzoneReportDefs.REASONSELFHARMED,
|
||||
},
|
||||
{
|
||||
title: _(msg`Dangerous challenges or activities`),
|
||||
reason: OzoneReportDefs.REASONSELFHARMSTUNTS,
|
||||
},
|
||||
{
|
||||
title: _(msg`Dangerous substances or drug abuse`),
|
||||
reason: OzoneReportDefs.REASONSELFHARMSUBSTANCES,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other dangerous content`),
|
||||
reason: OzoneReportDefs.REASONSELFHARMOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
ruleBreaking: {
|
||||
key: 'ruleBreaking',
|
||||
title: _(msg`Breaking site rules`),
|
||||
description: _(msg`Banned activities or security violations`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Hacking or system attacks`),
|
||||
reason: OzoneReportDefs.REASONRULESITESECURITY,
|
||||
},
|
||||
{
|
||||
title: _(msg`Promoting or selling prohibited items or services`),
|
||||
reason: OzoneReportDefs.REASONRULEPROHIBITEDSALES,
|
||||
},
|
||||
{
|
||||
title: _(msg`Banned user returning`),
|
||||
reason: OzoneReportDefs.REASONRULEBANEVASION,
|
||||
},
|
||||
{
|
||||
title: _(msg`Other network rule-breaking`),
|
||||
reason: OzoneReportDefs.REASONRULEOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
other: {
|
||||
key: 'other',
|
||||
title: _(msg`Other`),
|
||||
description: _(msg`An issue not included in these options`),
|
||||
options: [
|
||||
{
|
||||
title: _(msg`Other`),
|
||||
reason: OzoneReportDefs.REASONOTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
const common = [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONRUDE,
|
||||
title: _(msg`Anti-Social Behavior`),
|
||||
description: _(msg`Harassment, trolling, or intolerance`),
|
||||
},
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||
title: _(msg`Illegal and Urgent`),
|
||||
description: _(msg`Glaring violations of law or terms of service`),
|
||||
},
|
||||
other,
|
||||
]
|
||||
|
||||
return {
|
||||
account: [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONMISLEADING,
|
||||
title: _(msg`Misleading Account`),
|
||||
description: _(
|
||||
msg`Impersonation or false claims about identity or affiliation`,
|
||||
),
|
||||
},
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONSPAM,
|
||||
title: _(msg`Frequently Posts Unwanted Content`),
|
||||
description: _(msg`Spam; excessive mentions or replies`),
|
||||
},
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||
title: _(msg`Name or Description Violates Community Standards`),
|
||||
description: _(msg`Terms used violate community standards`),
|
||||
},
|
||||
other,
|
||||
],
|
||||
post: [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONMISLEADING,
|
||||
title: _(msg`Misleading Post`),
|
||||
description: _(msg`Impersonation, misinformation, or false claims`),
|
||||
},
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONSPAM,
|
||||
title: _(msg`Spam`),
|
||||
description: _(msg`Excessive mentions or replies`),
|
||||
},
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONSEXUAL,
|
||||
title: _(msg`Unwanted Sexual Content`),
|
||||
description: _(msg`Nudity or adult content not labeled as such`),
|
||||
},
|
||||
...common,
|
||||
],
|
||||
chatMessage: [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONSPAM,
|
||||
title: _(msg`Spam`),
|
||||
description: _(msg`Excessive or unwanted messages`),
|
||||
},
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONSEXUAL,
|
||||
title: _(msg`Unwanted Sexual Content`),
|
||||
description: _(msg`Inappropriate messages or explicit links`),
|
||||
},
|
||||
...common,
|
||||
],
|
||||
list: [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||
title: _(msg`Name or Description Violates Community Standards`),
|
||||
description: _(msg`Terms used violate community standards`),
|
||||
},
|
||||
...common,
|
||||
],
|
||||
starterPack: [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||
title: _(msg`Name or Description Violates Community Standards`),
|
||||
description: _(msg`Terms used violate community standards`),
|
||||
},
|
||||
...common,
|
||||
],
|
||||
feed: [
|
||||
{
|
||||
reason: ComAtprotoModerationDefs.REASONVIOLATION,
|
||||
title: _(msg`Name or Description Violates Community Standards`),
|
||||
description: _(msg`Terms used violate community standards`),
|
||||
},
|
||||
...common,
|
||||
],
|
||||
categories: Object.values(categories) as ReportCategoryConfig[],
|
||||
getCategory(reasonName: ReportCategory) {
|
||||
return categories[reasonName]
|
||||
},
|
||||
}
|
||||
}, [_])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user