From 4c83d5dfe34c2dea61c89445f146415e152ac919 Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Tue, 12 Mar 2024 18:57:56 +0000 Subject: [PATCH] Ozone namespace changes and decouple labels from sdk (#46) * :sparkles: Update ozone namespace * :sparkles: Hard code label definitions --- app/actions/ModActionPanel/BlobList.tsx | 9 +- app/actions/ModActionPanel/QuickAction.tsx | 18 +- app/actions/[id]/page.tsx | 2 +- app/events/[id]/page.tsx | 2 +- app/events/page-content.tsx | 4 +- app/reports/page-content.tsx | 24 +- .../[id]/[...record]/page-content.tsx | 6 +- app/repositories/[id]/page-content.tsx | 4 +- app/repositories/page-content.tsx | 2 +- app/subject-status/page.tsx | 2 +- components/common/RecordCard.tsx | 10 +- components/common/labels/data.ts | 1053 +++++++++++++++++ components/common/labels/util.ts | 12 +- .../delete-confirmation-modal.tsx | 2 +- components/communication-template/hooks.ts | 6 +- components/email/Composer.tsx | 2 +- components/email/helpers.ts | 6 +- components/mod-event/EventItem.tsx | 65 +- components/mod-event/ItemTitle.tsx | 28 +- components/mod-event/SelectorButton.tsx | 6 +- components/mod-event/View.tsx | 38 +- components/mod-event/constants.ts | 26 +- components/mod-event/helpers/emitEvent.tsx | 6 +- components/mod-event/helpers/subject.tsx | 3 +- components/mod-event/useModEventList.tsx | 10 +- .../reports/ActionView/getSubjectString.ts | 18 +- components/reports/ReportsTable.tsx | 219 ---- components/reports/helpers/subject.ts | 8 +- components/repositories/AccountView.tsx | 2 +- components/repositories/BlobsTable.tsx | 12 +- components/repositories/RecordView.tsx | 12 +- components/repositories/useRepoAndProfile.ts | 2 +- components/shell/CommandPalette/actions.ts | 8 +- components/subject/ReviewStateMarker.tsx | 26 +- components/subject/StatusView.tsx | 8 +- cypress/e2e/auth/main.cy.ts | 4 +- lib/client.ts | 2 +- lib/types.ts | 10 +- package.json | 2 +- yarn.lock | 48 +- 40 files changed, 1285 insertions(+), 442 deletions(-) create mode 100644 components/common/labels/data.ts delete mode 100644 components/reports/ReportsTable.tsx diff --git a/app/actions/ModActionPanel/BlobList.tsx b/app/actions/ModActionPanel/BlobList.tsx index 181eea5e..27d87c9a 100644 --- a/app/actions/ModActionPanel/BlobList.tsx +++ b/app/actions/ModActionPanel/BlobList.tsx @@ -1,6 +1,5 @@ import { ComponentProps } from 'react' -import Link from 'next/link' -import { ComAtprotoAdminDefs } from '@atproto/api' +import { ToolsOzoneModerationDefs } from '@atproto/api' import { ShieldExclamationIcon } from '@heroicons/react/20/solid' import { formatBytes } from '@/lib/util' import { ReviewStateIconLink } from '@/subject/ReviewStateMarker' @@ -8,7 +7,7 @@ import { ReviewStateIconLink } from '@/subject/ReviewStateMarker' export function BlobList(props: { name: string disabled?: boolean - blobs: ComAtprotoAdminDefs.BlobView[] + blobs: ToolsOzoneModerationDefs.BlobView[] }) { const { name, disabled, blobs } = props return ( @@ -52,8 +51,8 @@ export function BlobList(props: {

{blob.mimeType} {formatBytes(blob.size)} - {(ComAtprotoAdminDefs.isImageDetails(blob.details) || - ComAtprotoAdminDefs.isVideoDetails(blob.details)) && ( + {(ToolsOzoneModerationDefs.isImageDetails(blob.details) || + ToolsOzoneModerationDefs.isVideoDetails(blob.details)) && ( {blob.details.height}x{blob.details.width}px diff --git a/app/actions/ModActionPanel/QuickAction.tsx b/app/actions/ModActionPanel/QuickAction.tsx index 5d81a8d7..fe700c57 100644 --- a/app/actions/ModActionPanel/QuickAction.tsx +++ b/app/actions/ModActionPanel/QuickAction.tsx @@ -1,9 +1,9 @@ // TODO: This is badly named so that we can rebuild this component without breaking the old one import { useQuery } from '@tanstack/react-query' import { - ComAtprotoAdminDefs, - ComAtprotoAdminEmitModerationEvent, ComAtprotoModerationDefs, + ToolsOzoneModerationDefs, + ToolsOzoneModerationEmitEvent, } from '@atproto/api' import { FormEvent, useEffect, useRef, useState } from 'react' import { ActionPanel } from '@/common/ActionPanel' @@ -56,9 +56,7 @@ type Props = { setSubject: (subject: string) => void subjectOptions?: string[] isInitialLoading: boolean - onSubmit: ( - vals: ComAtprotoAdminEmitModerationEvent.InputSchema, - ) => Promise + onSubmit: (vals: ToolsOzoneModerationEmitEvent.InputSchema) => Promise } const dateFormatter = new Intl.DateTimeFormat('en-US', { @@ -170,9 +168,9 @@ function Form( }) const isSubjetDid = subject.startsWith('did:') const isReviewClosed = - subjectStatus?.reviewState === ComAtprotoAdminDefs.REVIEWCLOSED + subjectStatus?.reviewState === ToolsOzoneModerationDefs.REVIEWCLOSED const isEscalated = - subjectStatus?.reviewState === ComAtprotoAdminDefs.REVIEWESCALATED + subjectStatus?.reviewState === ToolsOzoneModerationDefs.REVIEWESCALATED const allLabels = getLabelsForSubject({ repo, record }) const currentLabels = allLabels.map((label) => @@ -740,7 +738,7 @@ function Form( async function getSubject(subject: string) { if (subject.startsWith('did:')) { - const { data: repo } = await client.api.com.atproto.admin.getRepo( + const { data: repo } = await client.api.tools.ozone.moderation.getRepo( { did: subject, }, @@ -748,7 +746,7 @@ async function getSubject(subject: string) { ) return { repo } } else if (subject.startsWith('at://')) { - const { data: record } = await client.api.com.atproto.admin.getRecord( + const { data: record } = await client.api.tools.ozone.moderation.getRecord( { uri: subject, }, @@ -763,7 +761,7 @@ async function getSubject(subject: string) { async function getSubjectStatus(subject: string) { const { data: { subjectStatuses }, - } = await client.api.com.atproto.admin.queryModerationStatuses( + } = await client.api.tools.ozone.moderation.queryStatuses( { subject, includeMuted: true, diff --git a/app/actions/[id]/page.tsx b/app/actions/[id]/page.tsx index ea74b31c..28056dae 100644 --- a/app/actions/[id]/page.tsx +++ b/app/actions/[id]/page.tsx @@ -10,7 +10,7 @@ export default function Action({ params }: { params: { id: string } }) { const { data: action, error } = useQuery({ queryKey: ['action', { id }], queryFn: async () => { - const { data } = await client.api.com.atproto.admin.getModerationEvent( + const { data } = await client.api.tools.ozone.moderation.getEvent( { id: parseInt(id, 10), }, diff --git a/app/events/[id]/page.tsx b/app/events/[id]/page.tsx index c1b58fec..6a6b7551 100644 --- a/app/events/[id]/page.tsx +++ b/app/events/[id]/page.tsx @@ -11,7 +11,7 @@ export default function EventViewPage({ params }: { params: { id: string } }) { const { data: event, error } = useQuery({ queryKey: ['event', { id }], queryFn: async () => { - const { data } = await client.api.com.atproto.admin.getModerationEvent( + const { data } = await client.api.tools.ozone.moderation.getEvent( { id: parseInt(id, 10), }, diff --git a/app/events/page-content.tsx b/app/events/page-content.tsx index 8df0075f..4b5c1914 100644 --- a/app/events/page-content.tsx +++ b/app/events/page-content.tsx @@ -1,7 +1,7 @@ import { useTitle } from 'react-use' import { ModEventList } from '@/mod-event/EventList' import { emitEvent } from '@/mod-event/helpers/emitEvent' -import { ComAtprotoAdminEmitModerationEvent } from '@atproto/api' +import { ToolsOzoneModerationEmitEvent } from '@atproto/api' import { ModActionPanelQuick } from 'app/actions/ModActionPanel/QuickAction' import { usePathname, useRouter, useSearchParams } from 'next/navigation' @@ -35,7 +35,7 @@ export default function EventListPageContent() { subjectOptions={[quickOpenParam]} isInitialLoading={false} onSubmit={async ( - vals: ComAtprotoAdminEmitModerationEvent.InputSchema, + vals: ToolsOzoneModerationEmitEvent.InputSchema, ) => { await emitEvent(vals) }} diff --git a/app/reports/page-content.tsx b/app/reports/page-content.tsx index 43042592..bfe08a2c 100644 --- a/app/reports/page-content.tsx +++ b/app/reports/page-content.tsx @@ -9,9 +9,9 @@ import { import { useInfiniteQuery } from '@tanstack/react-query' import { AtUri, - ComAtprotoAdminDefs, - ComAtprotoAdminEmitModerationEvent, - ComAtprotoAdminQueryModerationStatuses, + ToolsOzoneModerationDefs, + ToolsOzoneModerationEmitEvent, + ToolsOzoneModerationQueryStatuses } from '@atproto/api' import { SectionHeader } from '../../components/SectionHeader' import { ModActionIcon } from '@/common/ModActionIcon' @@ -32,21 +32,21 @@ const TABS = [ key: 'unresolved', name: 'Unresolved', href: `/reports?reviewState=${encodeURIComponent( - ComAtprotoAdminDefs.REVIEWOPEN, + ToolsOzoneModerationDefs.REVIEWOPEN, )}`, }, { key: 'escalated', name: 'Escalated', href: `/reports?reviewState=${encodeURIComponent( - ComAtprotoAdminDefs.REVIEWESCALATED, + ToolsOzoneModerationDefs.REVIEWESCALATED, )}`, }, { key: 'resolved', name: 'Resolved', href: `/reports?reviewState=${encodeURIComponent( - ComAtprotoAdminDefs.REVIEWCLOSED, + ToolsOzoneModerationDefs.REVIEWCLOSED, )}`, }, { key: 'all', name: 'All', href: '/reports' }, @@ -290,7 +290,7 @@ export const ReportsPageContent = () => { subjectOptions={subjectOptions} isInitialLoading={isInitialLoading} onSubmit={async ( - vals: ComAtprotoAdminEmitModerationEvent.InputSchema, + vals: ToolsOzoneModerationEmitEvent.InputSchema, ) => { await emitEvent(vals) refetch() @@ -302,9 +302,9 @@ export const ReportsPageContent = () => { function getTabFromParams({ reviewState }: { reviewState?: string | null }) { const reviewStateMap = { - [ComAtprotoAdminDefs.REVIEWESCALATED]: 'escalated', - [ComAtprotoAdminDefs.REVIEWCLOSED]: 'resolved', - [ComAtprotoAdminDefs.REVIEWOPEN]: 'unresolved', + [ToolsOzoneModerationDefs.REVIEWESCALATED]: 'escalated', + [ToolsOzoneModerationDefs.REVIEWCLOSED]: 'resolved', + [ToolsOzoneModerationDefs.REVIEWOPEN]: 'unresolved', } if (reviewState) { @@ -315,10 +315,10 @@ function getTabFromParams({ reviewState }: { reviewState?: string | null }) { } async function getModerationQueue( - opts: ComAtprotoAdminQueryModerationStatuses.QueryParams = {}, + opts: ToolsOzoneModerationQueryStatuses.QueryParams = {}, queueName: string | null, ) { - const { data } = await client.api.com.atproto.admin.queryModerationStatuses( + const { data } = await client.api.tools.ozone.moderation.queryStatuses( { limit: 50, includeMuted: true, diff --git a/app/repositories/[id]/[...record]/page-content.tsx b/app/repositories/[id]/[...record]/page-content.tsx index 14223cf1..9f67f050 100644 --- a/app/repositories/[id]/[...record]/page-content.tsx +++ b/app/repositories/[id]/[...record]/page-content.tsx @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { AppBskyFeedGetPostThread as GetPostThread, - ComAtprotoAdminEmitModerationEvent, + ToolsOzoneModerationEmitEvent, } from '@atproto/api' import { ReportPanel } from '@/reports/ReportPanel' import { RecordView } from '@/repositories/RecordView' @@ -75,7 +75,7 @@ export default function RecordViewPageContent({ const uri = createAtUri({ did, collection, rkey }) const getRecord = async () => { - const { data: record } = await client.api.com.atproto.admin.getRecord( + const { data: record } = await client.api.tools.ozone.moderation.getRecord( { uri }, { headers: client.proxyHeaders() }, ) @@ -171,7 +171,7 @@ export default function RecordViewPageContent({ subjectOptions={[quickOpenParam]} isInitialLoading={isInitialLoading} onSubmit={async ( - vals: ComAtprotoAdminEmitModerationEvent.InputSchema, + vals: ToolsOzoneModerationEmitEvent.InputSchema, ) => { await emitEvent(vals) refetch() diff --git a/app/repositories/[id]/page-content.tsx b/app/repositories/[id]/page-content.tsx index 2a8f29b2..16d613aa 100644 --- a/app/repositories/[id]/page-content.tsx +++ b/app/repositories/[id]/page-content.tsx @@ -2,7 +2,7 @@ import { AccountView } from '@/repositories/AccountView' import { createReport } from '@/repositories/createReport' import { useRepoAndProfile } from '@/repositories/useRepoAndProfile' -import { ComAtprotoAdminEmitModerationEvent } from '@atproto/api' +import { ToolsOzoneModerationEmitEvent } from '@atproto/api' import { ModActionPanelQuick } from 'app/actions/ModActionPanel/QuickAction' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { emitEvent } from '@/mod-event/helpers/emitEvent' @@ -67,7 +67,7 @@ export function RepositoryViewPageContent({ id }: { id: string }) { subjectOptions={[quickOpenParam]} isInitialLoading={isInitialLoading} onSubmit={async ( - vals: ComAtprotoAdminEmitModerationEvent.InputSchema, + vals: ToolsOzoneModerationEmitEvent.InputSchema, ) => { await emitEvent(vals) refetch() diff --git a/app/repositories/page-content.tsx b/app/repositories/page-content.tsx index 3bfb0189..eb2830f6 100644 --- a/app/repositories/page-content.tsx +++ b/app/repositories/page-content.tsx @@ -12,7 +12,7 @@ export default function RepositoriesListPage() { const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({ queryKey: ['repositories', { term }], queryFn: async ({ pageParam }) => { - const { data } = await client.api.com.atproto.admin.searchRepos( + const { data } = await client.api.tools.ozone.moderation.searchRepos( { term, limit: 25, diff --git a/app/subject-status/page.tsx b/app/subject-status/page.tsx index 9af27e64..d24c3357 100644 --- a/app/subject-status/page.tsx +++ b/app/subject-status/page.tsx @@ -15,7 +15,7 @@ export default function SubjectStatus() { queryFn: async () => { if (!subject) return null const { data } = - await client.api.com.atproto.admin.queryModerationStatuses( + await client.api.tools.ozone.moderation.queryStatuses( { subject, limit: 1 }, { headers: client.proxyHeaders() }, ) diff --git a/components/common/RecordCard.tsx b/components/common/RecordCard.tsx index 54316e67..a2925b1f 100644 --- a/components/common/RecordCard.tsx +++ b/components/common/RecordCard.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query' -import { AppBskyFeedDefs, ComAtprotoAdminDefs } from '@atproto/api' +import { AppBskyFeedDefs, ToolsOzoneModerationDefs } from '@atproto/api' import { buildBlueSkyAppUrl, parseAtUri } from '@/lib/util' import client from '@/lib/client' import { PostAsCard } from './posts/PostsFeed' @@ -84,14 +84,14 @@ function PostCard(props: { uri: string; showLabels?: boolean }) { function BaseRecordCard(props: { uri: string - renderRecord: (record: ComAtprotoAdminDefs.RecordViewDetail) => JSX.Element + renderRecord: (record: ToolsOzoneModerationDefs.RecordViewDetail) => JSX.Element }) { const { uri, renderRecord } = props const { data: record, error } = useQuery({ retry: false, queryKey: ['recordCard', { uri }], queryFn: async () => { - const { data } = await client.api.com.atproto.admin.getRecord( + const { data } = await client.api.tools.ozone.moderation.getRecord( { uri }, { headers: client.proxyHeaders() }, ) @@ -118,7 +118,7 @@ function GenericRecordCard({ record, did, }: { - record: ComAtprotoAdminDefs.RecordViewDetail + record: ToolsOzoneModerationDefs.RecordViewDetail did?: string }) { return ( @@ -138,7 +138,7 @@ const useRepoAndProfile = ({ did }: { did: string }) => { queryFn: async () => { // @TODO when unifying admin auth, ensure admin can see taken-down profiles const getRepo = async () => { - const { data: repo } = await client.api.com.atproto.admin.getRepo( + const { data: repo } = await client.api.tools.ozone.moderation.getRepo( { did }, { headers: client.proxyHeaders() }, ) diff --git a/components/common/labels/data.ts b/components/common/labels/data.ts new file mode 100644 index 00000000..9f04316c --- /dev/null +++ b/components/common/labels/data.ts @@ -0,0 +1,1053 @@ +// IMPORTANT: This is copy-pasted from previously defined labels in the @atproto/api sdk +// we should revisit this once the sdk update is complete and we are ready to align label definitions + +import { ComAtprotoLabelDefs } from '@atproto/api' + +export type Label = ComAtprotoLabelDefs.Label + +export type LabelPreference = 'ignore' | 'warn' | 'hide' +export type LabelDefinitionFlag = 'no-override' | 'adult' | 'unauthed' +export type LabelDefinitionOnWarnBehavior = + | 'blur' + | 'blur-media' + | 'alert' + | null + +export interface LabelDefinitionLocalizedStrings { + name: string + description: string +} + +export type LabelDefinitionLocalizedStringsMap = Record< + string, + LabelDefinitionLocalizedStrings +> + +export interface LabelDefinition { + id: string + groupId: string + configurable: boolean + preferences: LabelPreference[] + flags: LabelDefinitionFlag[] + onwarn: LabelDefinitionOnWarnBehavior + strings: { + settings: LabelDefinitionLocalizedStringsMap + account: LabelDefinitionLocalizedStringsMap + content: LabelDefinitionLocalizedStringsMap + } +} + +export interface LabelGroupDefinition { + id: string + configurable: boolean + labels: LabelDefinition[] + strings: { + settings: LabelDefinitionLocalizedStringsMap + } +} + +export type LabelDefinitionMap = Record +export type LabelGroupDefinitionMap = Record + +export const LABELS: LabelDefinitionMap = { + '!hide': { + id: '!hide', + preferences: ['hide'], + flags: ['no-override'], + onwarn: 'blur', + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Moderator Hide', + description: 'Moderator has chosen to hide the content.', + }, + }, + account: { + en: { + name: 'Content Blocked', + description: 'This account has been hidden by the moderators.', + }, + }, + content: { + en: { + name: 'Content Blocked', + description: 'This content has been hidden by the moderators.', + }, + }, + }, + }, + '!no-promote': { + id: '!no-promote', + preferences: ['hide'], + flags: [], + onwarn: null, + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Moderator Filter', + description: 'Moderator has chosen to filter the content from feeds.', + }, + }, + account: { + en: { + name: 'N/A', + description: 'N/A', + }, + }, + content: { + en: { + name: 'N/A', + description: 'N/A', + }, + }, + }, + }, + '!warn': { + id: '!warn', + preferences: ['warn'], + flags: [], + onwarn: 'blur', + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Moderator Warn', + description: + 'Moderator has chosen to set a general warning on the content.', + }, + }, + account: { + en: { + name: 'Content Warning', + description: + 'This account has received a general warning from moderators.', + }, + }, + content: { + en: { + name: 'Content Warning', + description: + 'This content has received a general warning from moderators.', + }, + }, + }, + }, + '!no-unauthenticated': { + id: '!no-unauthenticated', + preferences: ['hide'], + flags: ['no-override', 'unauthed'], + onwarn: 'blur', + groupId: 'system', + configurable: false, + strings: { + settings: { + en: { + name: 'Sign-in Required', + description: + 'This user has requested that their account only be shown to signed-in users.', + }, + }, + account: { + en: { + name: 'Sign-in Required', + description: + 'This user has requested that their account only be shown to signed-in users.', + }, + }, + content: { + en: { + name: 'Sign-in Required', + description: + 'This user has requested that their content only be shown to signed-in users.', + }, + }, + }, + }, + 'dmca-violation': { + id: 'dmca-violation', + preferences: ['hide'], + flags: ['no-override'], + onwarn: 'blur', + groupId: 'legal', + configurable: false, + strings: { + settings: { + en: { + name: 'Copyright Violation', + description: 'The content has received a DMCA takedown request.', + }, + }, + account: { + en: { + name: 'Copyright Violation', + description: + 'This account has received a DMCA takedown request. It will be restored if the concerns can be resolved.', + }, + }, + content: { + en: { + name: 'Copyright Violation', + description: + 'This content has received a DMCA takedown request. It will be restored if the concerns can be resolved.', + }, + }, + }, + }, + doxxing: { + id: 'doxxing', + preferences: ['hide'], + flags: ['no-override'], + onwarn: 'blur', + groupId: 'legal', + configurable: false, + strings: { + settings: { + en: { + name: 'Doxxing', + description: + 'Information that reveals private information about someone which has been shared without the consent of the subject.', + }, + }, + account: { + en: { + name: 'Doxxing', + description: + 'This account has been reported to publish private information about someone without their consent. This report is currently under review.', + }, + }, + content: { + en: { + name: 'Doxxing', + description: + 'This content has been reported to include private information about someone without their consent.', + }, + }, + }, + }, + porn: { + id: 'porn', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'sexual', + configurable: true, + strings: { + settings: { + en: { + name: 'Pornography', + description: + 'Images of full-frontal nudity (genitalia) in any sexualized context, or explicit sexual activity (meaning contact with genitalia or breasts) even if partially covered. Includes graphic sexual cartoons (often jokes/memes).', + }, + }, + account: { + en: { + name: 'Adult Content', + description: + 'This account contains imagery of full-frontal nudity or explicit sexual activity.', + }, + }, + content: { + en: { + name: 'Adult Content', + description: + 'This content contains imagery of full-frontal nudity or explicit sexual activity.', + }, + }, + }, + }, + sexual: { + id: 'sexual', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'sexual', + configurable: true, + strings: { + settings: { + en: { + name: 'Sexually Suggestive', + description: + 'Content that does not meet the level of "pornography", but is still sexual. Some common examples have been selfies and "hornyposting" with underwear on, or partially naked (naked but covered, eg with hands or from side perspective). Sheer/see-through nipples may end up in this category.', + }, + }, + account: { + en: { + name: 'Suggestive Content', + description: + 'This account contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.', + }, + }, + content: { + en: { + name: 'Suggestive Content', + description: + 'This content contains imagery which is sexually suggestive. Common examples include selfies in underwear or in partial undress.', + }, + }, + }, + }, + nudity: { + id: 'nudity', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'sexual', + configurable: true, + strings: { + settings: { + en: { + name: 'Nudity', + description: + 'Nudity which is not sexual, or that is primarily "artistic" in nature. For example: breastfeeding; classic art paintings and sculptures; newspaper images with some nudity; fashion modeling. "Erotic photography" is likely to end up in sexual or porn.', + }, + }, + account: { + en: { + name: 'Adult Content', + description: + 'This account contains imagery which portrays nudity in a non-sexual or artistic setting.', + }, + }, + content: { + en: { + name: 'Adult Content', + description: + 'This content contains imagery which portrays nudity in a non-sexual or artistic setting.', + }, + }, + }, + }, + nsfl: { + id: 'nsfl', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'NSFL', + description: + '"Not Suitable For Life." This includes graphic images like the infamous "goatse" (don\'t look it up).', + }, + }, + account: { + en: { + name: 'Graphic Imagery (NSFL)', + description: + 'This account contains graphic images which are often referred to as "Not Suitable For Life."', + }, + }, + content: { + en: { + name: 'Graphic Imagery (NSFL)', + description: + 'This content contains graphic images which are often referred to as "Not Suitable For Life."', + }, + }, + }, + }, + corpse: { + id: 'corpse', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Corpse', + description: + 'Visual image of a dead human body in any context. Includes war images, hanging, funeral caskets. Does not include all figurative cases (cartoons), but can include realistic figurative images or renderings.', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Corpse)', + description: + 'This account contains images of a dead human body in any context. Includes war images, hanging, funeral caskets.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Corpse)', + description: + 'This content contains images of a dead human body in any context. Includes war images, hanging, funeral caskets.', + }, + }, + }, + }, + gore: { + id: 'gore', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Gore', + description: + 'Intended for shocking images, typically involving blood or visible wounds.', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Gore)', + description: + 'This account contains shocking images involving blood or visible wounds.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Gore)', + description: + 'This content contains shocking images involving blood or visible wounds.', + }, + }, + }, + }, + torture: { + id: 'torture', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Torture', + description: + 'Depictions of torture of a human or animal (animal cruelty).', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Torture)', + description: + 'This account contains depictions of torture of a human or animal.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Torture)', + description: + 'This content contains depictions of torture of a human or animal.', + }, + }, + }, + }, + 'self-harm': { + id: 'self-harm', + preferences: ['ignore', 'warn', 'hide'], + flags: ['adult'], + onwarn: 'blur-media', + groupId: 'violence', + configurable: true, + strings: { + settings: { + en: { + name: 'Self-Harm', + description: + 'A visual depiction (photo or figurative) of cutting, suicide, or similar.', + }, + }, + account: { + en: { + name: 'Graphic Imagery (Self-Harm)', + description: + 'This account includes depictions of cutting, suicide, or other forms of self-harm.', + }, + }, + content: { + en: { + name: 'Graphic Imagery (Self-Harm)', + description: + 'This content includes depictions of cutting, suicide, or other forms of self-harm.', + }, + }, + }, + }, + 'intolerant-race': { + id: 'intolerant-race', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Racial Intolerance', + description: 'Hateful or intolerant content related to race.', + }, + }, + account: { + en: { + name: 'Intolerance (Racial)', + description: + 'This account includes hateful or intolerant content related to race.', + }, + }, + content: { + en: { + name: 'Intolerance (Racial)', + description: + 'This content includes hateful or intolerant views related to race.', + }, + }, + }, + }, + 'intolerant-gender': { + id: 'intolerant-gender', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Gender Intolerance', + description: + 'Hateful or intolerant content related to gender or gender identity.', + }, + }, + account: { + en: { + name: 'Intolerance (Gender)', + description: + 'This account includes hateful or intolerant content related to gender or gender identity.', + }, + }, + content: { + en: { + name: 'Intolerance (Gender)', + description: + 'This content includes hateful or intolerant views related to gender or gender identity.', + }, + }, + }, + }, + 'intolerant-sexual-orientation': { + id: 'intolerant-sexual-orientation', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Sexual Orientation Intolerance', + description: + 'Hateful or intolerant content related to sexual preferences.', + }, + }, + account: { + en: { + name: 'Intolerance (Orientation)', + description: + 'This account includes hateful or intolerant content related to sexual preferences.', + }, + }, + content: { + en: { + name: 'Intolerance (Orientation)', + description: + 'This content includes hateful or intolerant views related to sexual preferences.', + }, + }, + }, + }, + 'intolerant-religion': { + id: 'intolerant-religion', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Religious Intolerance', + description: + 'Hateful or intolerant content related to religious views or practices.', + }, + }, + account: { + en: { + name: 'Intolerance (Religious)', + description: + 'This account includes hateful or intolerant content related to religious views or practices.', + }, + }, + content: { + en: { + name: 'Intolerance (Religious)', + description: + 'This content includes hateful or intolerant views related to religious views or practices.', + }, + }, + }, + }, + intolerant: { + id: 'intolerant', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Intolerance', + description: + 'A catchall for hateful or intolerant content which is not covered elsewhere.', + }, + }, + account: { + en: { + name: 'Intolerance', + description: 'This account includes hateful or intolerant content.', + }, + }, + content: { + en: { + name: 'Intolerance', + description: 'This content includes hateful or intolerant views.', + }, + }, + }, + }, + 'icon-intolerant': { + id: 'icon-intolerant', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur-media', + groupId: 'intolerance', + configurable: true, + strings: { + settings: { + en: { + name: 'Intolerant Iconography', + description: + 'Visual imagery associated with a hate group, such as the KKK or Nazi, in any context (supportive, critical, documentary, etc).', + }, + }, + account: { + en: { + name: 'Intolerant Iconography', + description: + 'This account includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes.', + }, + }, + content: { + en: { + name: 'Intolerant Iconography', + description: + 'This content includes imagery associated with a hate group such as the KKK or Nazis. This warning may apply to content any context, including critical or documentary purposes.', + }, + }, + }, + }, + threat: { + id: 'threat', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'rude', + configurable: true, + strings: { + settings: { + en: { + name: 'Threats', + description: + 'Statements or imagery published with the intent to threaten, intimidate, or harm.', + }, + }, + account: { + en: { + name: 'Threats', + description: + 'The moderators believe this account has published statements or imagery with the intent to threaten, intimidate, or harm others.', + }, + }, + content: { + en: { + name: 'Threats', + description: + 'The moderators believe this content was published with the intent to threaten, intimidate, or harm others.', + }, + }, + }, + }, + spoiler: { + id: 'spoiler', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'curation', + configurable: true, + strings: { + settings: { + en: { + name: 'Spoiler', + description: + 'Discussion about film, TV, etc which gives away plot points.', + }, + }, + account: { + en: { + name: 'Spoiler Warning', + description: + 'This account contains discussion about film, TV, etc which gives away plot points.', + }, + }, + content: { + en: { + name: 'Spoiler Warning', + description: + 'This content contains discussion about film, TV, etc which gives away plot points.', + }, + }, + }, + }, + spam: { + id: 'spam', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'spam', + configurable: true, + strings: { + settings: { + en: { + name: 'Spam', + description: + 'Repeat, low-quality messages which are clearly not designed to add to a conversation or space.', + }, + }, + account: { + en: { + name: 'Spam', + description: + 'This account publishes repeat, low-quality messages which are clearly not designed to add to a conversation or space.', + }, + }, + content: { + en: { + name: 'Spam', + description: + 'This content is a part of repeat, low-quality messages which are clearly not designed to add to a conversation or space.', + }, + }, + }, + }, + 'account-security': { + id: 'account-security', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Security Concerns', + description: + 'Content designed to hijack user accounts such as a phishing attack.', + }, + }, + account: { + en: { + name: 'Security Warning', + description: + 'This account has published content designed to hijack user accounts such as a phishing attack.', + }, + }, + content: { + en: { + name: 'Security Warning', + description: + 'This content is designed to hijack user accounts such as a phishing attack.', + }, + }, + }, + }, + 'net-abuse': { + id: 'net-abuse', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'blur', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Network Attacks', + description: + 'Content designed to attack network systems such as denial-of-service attacks.', + }, + }, + account: { + en: { + name: 'Network Attack Warning', + description: + 'This account has published content designed to attack network systems such as denial-of-service attacks.', + }, + }, + content: { + en: { + name: 'Network Attack Warning', + description: + 'This content is designed to attack network systems such as denial-of-service attacks.', + }, + }, + }, + }, + impersonation: { + id: 'impersonation', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'alert', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Impersonation', + description: 'Accounts which falsely assert some identity.', + }, + }, + account: { + en: { + name: 'Impersonation Warning', + description: + 'The moderators believe this account is lying about their identity.', + }, + }, + content: { + en: { + name: 'Impersonation Warning', + description: + 'The moderators believe this account is lying about their identity.', + }, + }, + }, + }, + scam: { + id: 'scam', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'alert', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Scam', + description: 'Fraudulent content.', + }, + }, + account: { + en: { + name: 'Scam Warning', + description: + 'The moderators believe this account publishes fraudulent content.', + }, + }, + content: { + en: { + name: 'Scam Warning', + description: 'The moderators believe this is fraudulent content.', + }, + }, + }, + }, + misleading: { + id: 'misleading', + preferences: ['ignore', 'warn', 'hide'], + flags: [], + onwarn: 'alert', + groupId: 'misinfo', + configurable: true, + strings: { + settings: { + en: { + name: 'Misleading', + description: 'Accounts which share misleading information.', + }, + }, + account: { + en: { + name: 'Misleading', + description: + 'The moderators believe this account is spreading misleading information.', + }, + }, + content: { + en: { + name: 'Misleading', + description: + 'The moderators believe this account is spreading misleading information.', + }, + }, + }, + }, +} + +export const LABEL_GROUPS: LabelGroupDefinitionMap = { + system: { + id: 'system', + configurable: false, + labels: [ + LABELS['!hide'], + LABELS['!no-promote'], + LABELS['!warn'], + LABELS['!no-unauthenticated'], + ], + strings: { + settings: { + en: { + name: 'System', + description: 'Moderator overrides for special cases.', + }, + }, + }, + }, + legal: { + id: 'legal', + configurable: false, + labels: [LABELS['dmca-violation'], LABELS['doxxing']], + strings: { + settings: { + en: { + name: 'Legal', + description: 'Content removed for legal reasons.', + }, + }, + }, + }, + sexual: { + id: 'sexual', + configurable: true, + labels: [LABELS['porn'], LABELS['sexual'], LABELS['nudity']], + strings: { + settings: { + en: { + name: 'Adult Content', + description: 'Content which is sexual in nature.', + }, + }, + }, + }, + violence: { + id: 'violence', + configurable: true, + labels: [ + LABELS['nsfl'], + LABELS['corpse'], + LABELS['gore'], + LABELS['torture'], + LABELS['self-harm'], + ], + strings: { + settings: { + en: { + name: 'Violence', + description: 'Content which is violent or deeply disturbing.', + }, + }, + }, + }, + intolerance: { + id: 'intolerance', + configurable: true, + labels: [ + LABELS['intolerant-race'], + LABELS['intolerant-gender'], + LABELS['intolerant-sexual-orientation'], + LABELS['intolerant-religion'], + LABELS['intolerant'], + LABELS['icon-intolerant'], + ], + strings: { + settings: { + en: { + name: 'Intolerance', + description: + 'Content or behavior which is hateful or intolerant toward a group of people.', + }, + }, + }, + }, + rude: { + id: 'rude', + configurable: true, + labels: [LABELS['threat']], + strings: { + settings: { + en: { + name: 'Rude', + description: 'Behavior which is rude toward other users.', + }, + }, + }, + }, + curation: { + id: 'curation', + configurable: true, + labels: [LABELS['spoiler']], + strings: { + settings: { + en: { + name: 'Curational', + description: + 'Subjective moderation geared towards curating a more positive environment.', + }, + }, + }, + }, + spam: { + id: 'spam', + configurable: true, + labels: [LABELS['spam']], + strings: { + settings: { + en: { + name: 'Spam', + description: "Content which doesn't add to the conversation.", + }, + }, + }, + }, + misinfo: { + id: 'misinfo', + configurable: true, + labels: [ + LABELS['account-security'], + LABELS['net-abuse'], + LABELS['impersonation'], + LABELS['scam'], + LABELS['misleading'], + ], + strings: { + settings: { + en: { + name: 'Misinformation', + description: 'Content which misleads or defrauds users.', + }, + }, + }, + }, +} diff --git a/components/common/labels/util.ts b/components/common/labels/util.ts index cc7216b3..034eabe7 100644 --- a/components/common/labels/util.ts +++ b/components/common/labels/util.ts @@ -1,13 +1,15 @@ import { unique } from '@/lib/util' import { AppBskyActorDefs, - ComAtprotoAdminDefs, ComAtprotoLabelDefs, + ToolsOzoneModerationDefs, +} from '@atproto/api' +import { LabelDefinition, LabelGroupDefinition, LABELS, LABEL_GROUPS, -} from '@atproto/api' +} from './data' type LabelGroupInfoRecord = { color: string @@ -148,7 +150,7 @@ export const doesProfileNeedBlur = ({ repo, }: { profile?: AppBskyActorDefs.ProfileViewBasic - repo?: ComAtprotoAdminDefs.RepoView + repo?: ToolsOzoneModerationDefs.RepoView }) => { const labels: string[] = [] if (profile?.labels) { @@ -164,8 +166,8 @@ export const getLabelsForSubject = ({ repo, record, }: { - repo?: ComAtprotoAdminDefs.RepoViewDetail - record?: ComAtprotoAdminDefs.RecordViewDetail + repo?: ToolsOzoneModerationDefs.RepoViewDetail + record?: ToolsOzoneModerationDefs.RecordViewDetail }) => { return (record?.labels ?? repo?.labels ?? diff --git a/components/communication-template/delete-confirmation-modal.tsx b/components/communication-template/delete-confirmation-modal.tsx index cb71167c..3b8cdf91 100644 --- a/components/communication-template/delete-confirmation-modal.tsx +++ b/components/communication-template/delete-confirmation-modal.tsx @@ -21,7 +21,7 @@ export const CommunicationTemplateDeleteConfirmationModal = ({ const onDelete = async () => { setIsDeleting(true) try { - await clientManager.api.com.atproto.admin.deleteCommunicationTemplate( + await clientManager.api.tools.ozone.communication.deleteTemplate( { id: templateId }, { headers: clientManager.proxyHeaders(), encoding: 'application/json' }, ) diff --git a/components/communication-template/hooks.ts b/components/communication-template/hooks.ts index ec278774..77d7d3ee 100644 --- a/components/communication-template/hooks.ts +++ b/components/communication-template/hooks.ts @@ -20,7 +20,7 @@ export const useCommunicationTemplateList = ({ staleTime: 60 * 60 * 1000, queryFn: async () => { const { data } = - await client.api.com.atproto.admin.listCommunicationTemplates( + await client.api.tools.ozone.communication.listTemplates( {}, { headers: client.proxyHeaders() }, ) @@ -73,7 +73,7 @@ export const useCommunicationTemplateEditor = (templateId?: string) => { disabled: boolean }) => templateId - ? client.api.com.atproto.admin.updateCommunicationTemplate( + ? client.api.tools.ozone.communication.updateTemplate( { id: `${templateId}`, contentMarkdown, @@ -84,7 +84,7 @@ export const useCommunicationTemplateEditor = (templateId?: string) => { }, { encoding: 'application/json', headers: client.proxyHeaders() }, ) - : client.api.com.atproto.admin.createCommunicationTemplate( + : client.api.tools.ozone.communication.createTemplate( { contentMarkdown, subject, diff --git a/components/email/Composer.tsx b/components/email/Composer.tsx index 2503e0ce..88cc7502 100644 --- a/components/email/Composer.tsx +++ b/components/email/Composer.tsx @@ -51,7 +51,7 @@ export const EmailComposer = ({ did }: { did: string }) => { .toString() await toast.promise( - client.api.com.atproto.admin.emitModerationEvent( + client.api.tools.ozone.moderation.emitEvent( { event: { $type: MOD_EVENTS.EMAIL, diff --git a/components/email/helpers.ts b/components/email/helpers.ts index 2d5d3e0c..f28e1482 100644 --- a/components/email/helpers.ts +++ b/components/email/helpers.ts @@ -1,9 +1,9 @@ -import { ComAtprotoAdminDefs } from '@atproto/api' +import { ToolsOzoneCommunicationDefs } from '@atproto/api' export const getTemplate = ( templateName: string, - templateList: ComAtprotoAdminDefs.CommunicationTemplateView[], -): ComAtprotoAdminDefs.CommunicationTemplateView | undefined => { + templateList: ToolsOzoneCommunicationDefs.TemplateView[], +): ToolsOzoneCommunicationDefs.TemplateView | undefined => { return templateList.find((template) => template.name === templateName) } diff --git a/components/mod-event/EventItem.tsx b/components/mod-event/EventItem.tsx index 7239b491..0e5ce571 100644 --- a/components/mod-event/EventItem.tsx +++ b/components/mod-event/EventItem.tsx @@ -7,18 +7,21 @@ import { unFlagSelfLabel, } from '@/common/labels' import { ReasonBadge } from '@/reports/ReasonBadge' -import { ComAtprotoAdminDefs, ComAtprotoModerationDefs } from '@atproto/api' +import { + ToolsOzoneModerationDefs, + ComAtprotoModerationDefs, +} from '@atproto/api' import { MOD_EVENTS } from './constants' import { ItemTitle } from './ItemTitle' const Comment = ({ modEvent, }: { - modEvent: ComAtprotoAdminDefs.ModEventView & { + modEvent: ToolsOzoneModerationDefs.ModEventView & { event: - | ComAtprotoAdminDefs.ModEventEscalate - | ComAtprotoAdminDefs.ModEventAcknowledge - | ComAtprotoAdminDefs.ModEventComment + | ToolsOzoneModerationDefs.ModEventEscalate + | ToolsOzoneModerationDefs.ModEventAcknowledge + | ToolsOzoneModerationDefs.ModEventComment } }) => { return ( @@ -53,8 +56,8 @@ const Comment = ({ const Email = ({ modEvent, }: { - modEvent: ComAtprotoAdminDefs.ModEventView & { - event: ComAtprotoAdminDefs.ModEventEmail + modEvent: ToolsOzoneModerationDefs.ModEventView & { + event: ToolsOzoneModerationDefs.ModEventEmail } }) => { return ( @@ -77,8 +80,8 @@ const Report = ({ modEvent, }: { modEvent: { - event: ComAtprotoAdminDefs.ModEventReport - } & ComAtprotoAdminDefs.ModEventView + event: ToolsOzoneModerationDefs.ModEventReport + } & ToolsOzoneModerationDefs.ModEventView }) => { const isAppeal = modEvent.event.reportType === ComAtprotoModerationDefs.REASONAPPEAL @@ -107,9 +110,9 @@ const TakedownOrMute = ({ }: { modEvent: { event: - | ComAtprotoAdminDefs.ModEventTakedown - | ComAtprotoAdminDefs.ModEventMute - } & ComAtprotoAdminDefs.ModEventView + | ToolsOzoneModerationDefs.ModEventTakedown + | ToolsOzoneModerationDefs.ModEventMute + } & ToolsOzoneModerationDefs.ModEventView }) => { const expiresAt = getExpiresAtFromEvent(modEvent) return ( @@ -173,8 +176,8 @@ const Label = ({ modEvent, }: { modEvent: { - event: ComAtprotoAdminDefs.ModEventLabel - } & ComAtprotoAdminDefs.ModEventView + event: ToolsOzoneModerationDefs.ModEventLabel + } & ToolsOzoneModerationDefs.ModEventView }) => { return ( @@ -199,8 +202,8 @@ const Tag = ({ modEvent, }: { modEvent: { - event: ComAtprotoAdminDefs.ModEventTag - } & ComAtprotoAdminDefs.ModEventView + event: ToolsOzoneModerationDefs.ModEventTag + } & ToolsOzoneModerationDefs.ModEventView }) => { return ( @@ -226,7 +229,9 @@ const dateFormatter = new Intl.DateTimeFormat('en-US', { timeStyle: 'short', }) -const getExpiresAtFromEvent = (modEvent: ComAtprotoAdminDefs.ModEventView) => { +const getExpiresAtFromEvent = ( + modEvent: ToolsOzoneModerationDefs.ModEventView, +) => { if (!modEvent.event.durationInHours) return null const createdAt = new Date(modEvent.createdAt) createdAt.setHours( @@ -240,26 +245,26 @@ export const ModEventItem = ({ showContentDetails, showContentAuthor, }: { - modEvent: ComAtprotoAdminDefs.ModEventView + modEvent: ToolsOzoneModerationDefs.ModEventView showContentDetails: boolean showContentAuthor: boolean }) => { let eventItem: JSX.Element =

{modEvent.event.$type as string}

if ( - ComAtprotoAdminDefs.isModEventAcknowledge(modEvent.event) || - ComAtprotoAdminDefs.isModEventEscalate(modEvent.event) || - ComAtprotoAdminDefs.isModEventComment(modEvent.event) || - ComAtprotoAdminDefs.isModEventUnmute(modEvent.event) || - ComAtprotoAdminDefs.isModEventResolveAppeal(modEvent.event) || - ComAtprotoAdminDefs.isModEventReverseTakedown(modEvent.event) || + ToolsOzoneModerationDefs.isModEventAcknowledge(modEvent.event) || + ToolsOzoneModerationDefs.isModEventEscalate(modEvent.event) || + ToolsOzoneModerationDefs.isModEventComment(modEvent.event) || + ToolsOzoneModerationDefs.isModEventUnmute(modEvent.event) || + ToolsOzoneModerationDefs.isModEventResolveAppeal(modEvent.event) || + ToolsOzoneModerationDefs.isModEventReverseTakedown(modEvent.event) || // This is temporary since the api package with this new type check is not yet published - modEvent.event.$type === 'com.atproto.admin.defs#modEventDivert' + modEvent.event.$type === 'tools.ozone.moderation.defs#modEventDivert' ) { eventItem = } if ( - ComAtprotoAdminDefs.isModEventTakedown(modEvent.event) || - ComAtprotoAdminDefs.isModEventMute(modEvent.event) + ToolsOzoneModerationDefs.isModEventTakedown(modEvent.event) || + ToolsOzoneModerationDefs.isModEventMute(modEvent.event) ) { eventItem = } @@ -267,15 +272,15 @@ export const ModEventItem = ({ //@ts-ignore eventItem = } - if (ComAtprotoAdminDefs.isModEventLabel(modEvent.event)) { + if (ToolsOzoneModerationDefs.isModEventLabel(modEvent.event)) { //@ts-ignore eventItem =