From 58551bbe0595462c44fc3b6ab5b83e520f141933 Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Thu, 25 Apr 2024 16:43:04 +0200 Subject: [PATCH] :sparkles: Allow muting reporter (#2390) * :sparkles: Allow muting reporter * :sparkles: Allow fetching ONLY muted subjects * :rotating_light: re-run linter on fixed main * :sparkles: Track muted reports * :white_check_mark: Adjust snapshot * :white_check_mark: Adjust snapshot * :white_check_mark: Adjust snapshot * :memo: Add changesets * :recycle: Refactor muted reporter check * :sparkles: Use new event type for muting/unmuting reporter * :broom: Cleanup --- .changeset/eleven-timers-share.md | 7 ++ lexicons/tools/ozone/moderation/defs.json | 36 +++++++ .../tools/ozone/moderation/emitEvent.json | 3 + .../tools/ozone/moderation/queryStatuses.json | 4 + packages/api/src/client/lexicons.ts | 47 ++++++++ .../types/tools/ozone/moderation/defs.ts | 56 ++++++++++ .../types/tools/ozone/moderation/emitEvent.ts | 3 + .../tools/ozone/moderation/queryStatuses.ts | 2 + .../ozone/src/api/moderation/emitEvent.ts | 9 ++ .../ozone/src/api/moderation/queryStatuses.ts | 2 + packages/ozone/src/api/util.ts | 2 + .../20240408T192432676Z-mute-reporting.ts | 15 +++ packages/ozone/src/db/migrations/index.ts | 1 + .../ozone/src/db/schema/moderation_event.ts | 3 + .../db/schema/moderation_subject_status.ts | 1 + packages/ozone/src/lexicon/lexicons.ts | 47 ++++++++ .../types/tools/ozone/moderation/defs.ts | 56 ++++++++++ .../types/tools/ozone/moderation/emitEvent.ts | 3 + .../tools/ozone/moderation/queryStatuses.ts | 2 + packages/ozone/src/mod-service/index.ts | 33 ++++++ packages/ozone/src/mod-service/status.ts | 26 +++++ packages/ozone/src/mod-service/views.ts | 3 + .../moderation-events.test.ts.snap | 5 + packages/ozone/tests/report-muting.test.ts | 100 ++++++++++++++++++ packages/pds/src/db/tables/moderation.ts | 6 ++ packages/pds/src/lexicon/lexicons.ts | 47 ++++++++ .../types/tools/ozone/moderation/defs.ts | 56 ++++++++++ .../types/tools/ozone/moderation/emitEvent.ts | 3 + .../tools/ozone/moderation/queryStatuses.ts | 2 + .../proxied/__snapshots__/admin.test.ts.snap | 2 + 30 files changed, 582 insertions(+) create mode 100644 .changeset/eleven-timers-share.md create mode 100644 packages/ozone/src/db/migrations/20240408T192432676Z-mute-reporting.ts create mode 100644 packages/ozone/tests/report-muting.test.ts diff --git a/.changeset/eleven-timers-share.md b/.changeset/eleven-timers-share.md new file mode 100644 index 00000000000..1b8630d5ee5 --- /dev/null +++ b/.changeset/eleven-timers-share.md @@ -0,0 +1,7 @@ +--- +"@atproto/ozone": patch +"@atproto/api": patch +"@atproto/pds": patch +--- + +Allow muting reports from accounts via `#modEventMuteReporter` event diff --git a/lexicons/tools/ozone/moderation/defs.json b/lexicons/tools/ozone/moderation/defs.json index e88ac98303e..51dd2460609 100644 --- a/lexicons/tools/ozone/moderation/defs.json +++ b/lexicons/tools/ozone/moderation/defs.json @@ -25,6 +25,9 @@ "#modEventAcknowledge", "#modEventEscalate", "#modEventMute", + "#modEventUnmute", + "#modEventMuteReporter", + "#modEventUnmuteReporter", "#modEventEmail", "#modEventResolveAppeal", "#modEventDivert" @@ -67,6 +70,9 @@ "#modEventAcknowledge", "#modEventEscalate", "#modEventMute", + "#modEventUnmute", + "#modEventMuteReporter", + "#modEventUnmuteReporter", "#modEventEmail", "#modEventResolveAppeal", "#modEventDivert" @@ -128,6 +134,10 @@ "type": "string", "format": "datetime" }, + "muteReportingUntil": { + "type": "string", + "format": "datetime" + }, "lastReviewedBy": { "type": "string", "format": "did" @@ -242,6 +252,10 @@ "comment": { "type": "string" }, + "isReporterMuted": { + "type": "boolean", + "description": "Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject." + }, "reportType": { "type": "ref", "ref": "com.atproto.moderation.defs#reasonType" @@ -300,6 +314,28 @@ } } }, + "modEventMuteReporter": { + "type": "object", + "description": "Mute incoming reports from an account", + "required": ["durationInHours"], + "properties": { + "comment": { "type": "string" }, + "durationInHours": { + "type": "integer", + "description": "Indicates how long the account should remain muted." + } + } + }, + "modEventUnmuteReporter": { + "type": "object", + "description": "Unmute incoming reports from an account", + "properties": { + "comment": { + "type": "string", + "description": "Describe reasoning behind the reversal." + } + } + }, "modEventEmail": { "type": "object", "description": "Keep a log of outgoing email to a user", diff --git a/lexicons/tools/ozone/moderation/emitEvent.json b/lexicons/tools/ozone/moderation/emitEvent.json index 32c12065008..cae73d8fbcd 100644 --- a/lexicons/tools/ozone/moderation/emitEvent.json +++ b/lexicons/tools/ozone/moderation/emitEvent.json @@ -21,6 +21,9 @@ "tools.ozone.moderation.defs#modEventLabel", "tools.ozone.moderation.defs#modEventReport", "tools.ozone.moderation.defs#modEventMute", + "tools.ozone.moderation.defs#modEventUnmute", + "tools.ozone.moderation.defs#modEventMuteReporter", + "tools.ozone.moderation.defs#modEventUnmuteReporter", "tools.ozone.moderation.defs#modEventReverseTakedown", "tools.ozone.moderation.defs#modEventUnmute", "tools.ozone.moderation.defs#modEventEmail", diff --git a/lexicons/tools/ozone/moderation/queryStatuses.json b/lexicons/tools/ozone/moderation/queryStatuses.json index 624ffbbb4ad..81fc1e38f33 100644 --- a/lexicons/tools/ozone/moderation/queryStatuses.json +++ b/lexicons/tools/ozone/moderation/queryStatuses.json @@ -37,6 +37,10 @@ "type": "boolean", "description": "By default, we don't include muted subjects in the results. Set this to true to include them." }, + "onlyMuted": { + "type": "boolean", + "description": "When set to true, only muted subjects and reporters will be returned." + }, "reviewState": { "type": "string", "description": "Specify when fetching subjects in a certain state" diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 2cbb180eb96..f2886955a36 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -8408,6 +8408,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventAcknowledge', 'lex:tools.ozone.moderation.defs#modEventEscalate', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventEmail', 'lex:tools.ozone.moderation.defs#modEventResolveAppeal', 'lex:tools.ozone.moderation.defs#modEventDivert', @@ -8467,6 +8470,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventAcknowledge', 'lex:tools.ozone.moderation.defs#modEventEscalate', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventEmail', 'lex:tools.ozone.moderation.defs#modEventResolveAppeal', 'lex:tools.ozone.moderation.defs#modEventDivert', @@ -8546,6 +8552,10 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + muteReportingUntil: { + type: 'string', + format: 'datetime', + }, lastReviewedBy: { type: 'string', format: 'did', @@ -8669,6 +8679,11 @@ export const schemaDict = { comment: { type: 'string', }, + isReporterMuted: { + type: 'boolean', + description: + "Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject.", + }, reportType: { type: 'ref', ref: 'lex:com.atproto.moderation.defs#reasonType', @@ -8737,6 +8752,30 @@ export const schemaDict = { }, }, }, + modEventMuteReporter: { + type: 'object', + description: 'Mute incoming reports from an account', + required: ['durationInHours'], + properties: { + comment: { + type: 'string', + }, + durationInHours: { + type: 'integer', + description: 'Indicates how long the account should remain muted.', + }, + }, + }, + modEventUnmuteReporter: { + type: 'object', + description: 'Unmute incoming reports from an account', + properties: { + comment: { + type: 'string', + description: 'Describe reasoning behind the reversal.', + }, + }, + }, modEventEmail: { type: 'object', description: 'Keep a log of outgoing email to a user', @@ -9121,6 +9160,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventLabel', 'lex:tools.ozone.moderation.defs#modEventReport', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventReverseTakedown', 'lex:tools.ozone.moderation.defs#modEventUnmute', 'lex:tools.ozone.moderation.defs#modEventEmail', @@ -9429,6 +9471,11 @@ export const schemaDict = { description: "By default, we don't include muted subjects in the results. Set this to true to include them.", }, + onlyMuted: { + type: 'boolean', + description: + 'When set to true, only muted subjects and reporters will be returned.', + }, reviewState: { type: 'string', description: 'Specify when fetching subjects in a certain state', diff --git a/packages/api/src/client/types/tools/ozone/moderation/defs.ts b/packages/api/src/client/types/tools/ozone/moderation/defs.ts index f6f546b6bef..0277ad1b6c8 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/defs.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/defs.ts @@ -22,6 +22,9 @@ export interface ModEventView { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventUnmute + | ModEventMuteReporter + | ModEventUnmuteReporter | ModEventEmail | ModEventResolveAppeal | ModEventDivert @@ -61,6 +64,9 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventUnmute + | ModEventMuteReporter + | ModEventUnmuteReporter | ModEventEmail | ModEventResolveAppeal | ModEventDivert @@ -105,6 +111,7 @@ export interface SubjectStatusView { /** Sticky comment on the subject. */ comment?: string muteUntil?: string + muteReportingUntil?: string lastReviewedBy?: string lastReviewedAt?: string lastReportedAt?: string @@ -237,6 +244,8 @@ export function validateModEventComment(v: unknown): ValidationResult { /** Report a subject */ export interface ModEventReport { comment?: string + /** Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject. */ + isReporterMuted?: boolean reportType: ComAtprotoModerationDefs.ReasonType [k: string]: unknown } @@ -346,6 +355,53 @@ export function validateModEventUnmute(v: unknown): ValidationResult { return lexicons.validate('tools.ozone.moderation.defs#modEventUnmute', v) } +/** Mute incoming reports from an account */ +export interface ModEventMuteReporter { + comment?: string + /** Indicates how long the account should remain muted. */ + durationInHours: number + [k: string]: unknown +} + +export function isModEventMuteReporter(v: unknown): v is ModEventMuteReporter { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'tools.ozone.moderation.defs#modEventMuteReporter' + ) +} + +export function validateModEventMuteReporter(v: unknown): ValidationResult { + return lexicons.validate( + 'tools.ozone.moderation.defs#modEventMuteReporter', + v, + ) +} + +/** Unmute incoming reports from an account */ +export interface ModEventUnmuteReporter { + /** Describe reasoning behind the reversal. */ + comment?: string + [k: string]: unknown +} + +export function isModEventUnmuteReporter( + v: unknown, +): v is ModEventUnmuteReporter { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'tools.ozone.moderation.defs#modEventUnmuteReporter' + ) +} + +export function validateModEventUnmuteReporter(v: unknown): ValidationResult { + return lexicons.validate( + 'tools.ozone.moderation.defs#modEventUnmuteReporter', + v, + ) +} + /** Keep a log of outgoing email to a user */ export interface ModEventEmail { /** The subject line of the email sent to the user. */ diff --git a/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts b/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts index 49ad72d208c..153dbbe9885 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/emitEvent.ts @@ -21,6 +21,9 @@ export interface InputSchema { | ToolsOzoneModerationDefs.ModEventLabel | ToolsOzoneModerationDefs.ModEventReport | ToolsOzoneModerationDefs.ModEventMute + | ToolsOzoneModerationDefs.ModEventUnmute + | ToolsOzoneModerationDefs.ModEventMuteReporter + | ToolsOzoneModerationDefs.ModEventUnmuteReporter | ToolsOzoneModerationDefs.ModEventReverseTakedown | ToolsOzoneModerationDefs.ModEventUnmute | ToolsOzoneModerationDefs.ModEventEmail diff --git a/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts b/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts index 55701ca94d4..10453220a33 100644 --- a/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts +++ b/packages/api/src/client/types/tools/ozone/moderation/queryStatuses.ts @@ -22,6 +22,8 @@ export interface QueryParams { reviewedBefore?: string /** By default, we don't include muted subjects in the results. Set this to true to include them. */ includeMuted?: boolean + /** When set to true, only muted subjects and reporters will be returned. */ + onlyMuted?: boolean /** Specify when fetching subjects in a certain state */ reviewState?: string ignoreSubjects?: string[] diff --git a/packages/ozone/src/api/moderation/emitEvent.ts b/packages/ozone/src/api/moderation/emitEvent.ts index 1d971811673..78fc850f6b0 100644 --- a/packages/ozone/src/api/moderation/emitEvent.ts +++ b/packages/ozone/src/api/moderation/emitEvent.ts @@ -5,8 +5,10 @@ import { isModEventDivert, isModEventEmail, isModEventLabel, + isModEventMuteReporter, isModEventReverseTakedown, isModEventTakedown, + isModEventUnmuteReporter, } from '../../lexicon/types/tools/ozone/moderation/defs' import { HandlerInput } from '../../lexicon/types/tools/ozone/moderation/emitEvent' import { subjectFromInput } from '../../mod-service/subject' @@ -113,6 +115,13 @@ const handleModerationEvent = async ({ await ctx.blobDiverter.uploadBlobOnService(subject.info()) } + if ( + (isModEventMuteReporter(event) || isModEventUnmuteReporter(event)) && + !subject.isRepo() + ) { + throw new InvalidRequestError('Subject must be a repo when muting reporter') + } + const moderationEvent = await db.transaction(async (dbTxn) => { const moderationTxn = ctx.modService(dbTxn) diff --git a/packages/ozone/src/api/moderation/queryStatuses.ts b/packages/ozone/src/api/moderation/queryStatuses.ts index fb433bdead9..5998a0c2390 100644 --- a/packages/ozone/src/api/moderation/queryStatuses.ts +++ b/packages/ozone/src/api/moderation/queryStatuses.ts @@ -20,6 +20,7 @@ export default function (server: Server, ctx: AppContext) { sortDirection = 'desc', sortField = 'lastReportedAt', includeMuted = false, + onlyMuted = false, limit = 50, cursor, tags = [], @@ -37,6 +38,7 @@ export default function (server: Server, ctx: AppContext) { reportedAfter, reportedBefore, includeMuted, + onlyMuted, ignoreSubjects, sortDirection, lastReviewedBy, diff --git a/packages/ozone/src/api/util.ts b/packages/ozone/src/api/util.ts index b9c1de4fb65..e5ed3ee2a46 100644 --- a/packages/ozone/src/api/util.ts +++ b/packages/ozone/src/api/util.ts @@ -112,6 +112,8 @@ const eventTypes = new Set([ 'tools.ozone.moderation.defs#modEventReport', 'tools.ozone.moderation.defs#modEventMute', 'tools.ozone.moderation.defs#modEventUnmute', + 'tools.ozone.moderation.defs#modEventMuteReporter', + 'tools.ozone.moderation.defs#modEventUnmuteReporter', 'tools.ozone.moderation.defs#modEventReverseTakedown', 'tools.ozone.moderation.defs#modEventEmail', 'tools.ozone.moderation.defs#modEventResolveAppeal', diff --git a/packages/ozone/src/db/migrations/20240408T192432676Z-mute-reporting.ts b/packages/ozone/src/db/migrations/20240408T192432676Z-mute-reporting.ts new file mode 100644 index 00000000000..0586cf6874f --- /dev/null +++ b/packages/ozone/src/db/migrations/20240408T192432676Z-mute-reporting.ts @@ -0,0 +1,15 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('moderation_subject_status') + .addColumn('muteReportingUntil', 'varchar') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('moderation_subject_status') + .dropColumn('muteReportingUntil') + .execute() +} diff --git a/packages/ozone/src/db/migrations/index.ts b/packages/ozone/src/db/migrations/index.ts index 1281f12c7f1..88d31dddcc2 100644 --- a/packages/ozone/src/db/migrations/index.ts +++ b/packages/ozone/src/db/migrations/index.ts @@ -7,3 +7,4 @@ export * as _20240116T085607200Z from './20240116T085607200Z-communication-templ export * as _20240201T051104136Z from './20240201T051104136Z-mod-event-blobs' export * as _20240208T213404429Z from './20240208T213404429Z-add-tags-column-to-moderation-subject' export * as _20240228T003647759Z from './20240228T003647759Z-add-label-sigs' +export * as _20240408T192432676Z from './20240408T192432676Z-mute-reporting' diff --git a/packages/ozone/src/db/schema/moderation_event.ts b/packages/ozone/src/db/schema/moderation_event.ts index aa43bcbe851..e860d52eb22 100644 --- a/packages/ozone/src/db/schema/moderation_event.ts +++ b/packages/ozone/src/db/schema/moderation_event.ts @@ -12,6 +12,9 @@ export interface ModerationEvent { | 'tools.ozone.moderation.defs#modEventLabel' | 'tools.ozone.moderation.defs#modEventReport' | 'tools.ozone.moderation.defs#modEventMute' + | 'tools.ozone.moderation.defs#modEventUnmute' + | 'tools.ozone.moderation.defs#modEventMuteReporter' + | 'tools.ozone.moderation.defs#modEventUnmuteReporter' | 'tools.ozone.moderation.defs#modEventReverseTakedown' | 'tools.ozone.moderation.defs#modEventEmail' | 'tools.ozone.moderation.defs#modEventResolveAppeal' diff --git a/packages/ozone/src/db/schema/moderation_subject_status.ts b/packages/ozone/src/db/schema/moderation_subject_status.ts index dd93f092db6..82438c1dff3 100644 --- a/packages/ozone/src/db/schema/moderation_subject_status.ts +++ b/packages/ozone/src/db/schema/moderation_subject_status.ts @@ -26,6 +26,7 @@ export interface ModerationSubjectStatus { lastReportedAt: string | null lastAppealedAt: string | null muteUntil: string | null + muteReportingUntil: string | null suspendUntil: string | null takendown: boolean appealed: boolean | null diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 2cbb180eb96..f2886955a36 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -8408,6 +8408,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventAcknowledge', 'lex:tools.ozone.moderation.defs#modEventEscalate', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventEmail', 'lex:tools.ozone.moderation.defs#modEventResolveAppeal', 'lex:tools.ozone.moderation.defs#modEventDivert', @@ -8467,6 +8470,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventAcknowledge', 'lex:tools.ozone.moderation.defs#modEventEscalate', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventEmail', 'lex:tools.ozone.moderation.defs#modEventResolveAppeal', 'lex:tools.ozone.moderation.defs#modEventDivert', @@ -8546,6 +8552,10 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + muteReportingUntil: { + type: 'string', + format: 'datetime', + }, lastReviewedBy: { type: 'string', format: 'did', @@ -8669,6 +8679,11 @@ export const schemaDict = { comment: { type: 'string', }, + isReporterMuted: { + type: 'boolean', + description: + "Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject.", + }, reportType: { type: 'ref', ref: 'lex:com.atproto.moderation.defs#reasonType', @@ -8737,6 +8752,30 @@ export const schemaDict = { }, }, }, + modEventMuteReporter: { + type: 'object', + description: 'Mute incoming reports from an account', + required: ['durationInHours'], + properties: { + comment: { + type: 'string', + }, + durationInHours: { + type: 'integer', + description: 'Indicates how long the account should remain muted.', + }, + }, + }, + modEventUnmuteReporter: { + type: 'object', + description: 'Unmute incoming reports from an account', + properties: { + comment: { + type: 'string', + description: 'Describe reasoning behind the reversal.', + }, + }, + }, modEventEmail: { type: 'object', description: 'Keep a log of outgoing email to a user', @@ -9121,6 +9160,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventLabel', 'lex:tools.ozone.moderation.defs#modEventReport', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventReverseTakedown', 'lex:tools.ozone.moderation.defs#modEventUnmute', 'lex:tools.ozone.moderation.defs#modEventEmail', @@ -9429,6 +9471,11 @@ export const schemaDict = { description: "By default, we don't include muted subjects in the results. Set this to true to include them.", }, + onlyMuted: { + type: 'boolean', + description: + 'When set to true, only muted subjects and reporters will be returned.', + }, reviewState: { type: 'string', description: 'Specify when fetching subjects in a certain state', diff --git a/packages/ozone/src/lexicon/types/tools/ozone/moderation/defs.ts b/packages/ozone/src/lexicon/types/tools/ozone/moderation/defs.ts index b150d735f6c..d5cfd9f5530 100644 --- a/packages/ozone/src/lexicon/types/tools/ozone/moderation/defs.ts +++ b/packages/ozone/src/lexicon/types/tools/ozone/moderation/defs.ts @@ -22,6 +22,9 @@ export interface ModEventView { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventUnmute + | ModEventMuteReporter + | ModEventUnmuteReporter | ModEventEmail | ModEventResolveAppeal | ModEventDivert @@ -61,6 +64,9 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventUnmute + | ModEventMuteReporter + | ModEventUnmuteReporter | ModEventEmail | ModEventResolveAppeal | ModEventDivert @@ -105,6 +111,7 @@ export interface SubjectStatusView { /** Sticky comment on the subject. */ comment?: string muteUntil?: string + muteReportingUntil?: string lastReviewedBy?: string lastReviewedAt?: string lastReportedAt?: string @@ -237,6 +244,8 @@ export function validateModEventComment(v: unknown): ValidationResult { /** Report a subject */ export interface ModEventReport { comment?: string + /** Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject. */ + isReporterMuted?: boolean reportType: ComAtprotoModerationDefs.ReasonType [k: string]: unknown } @@ -346,6 +355,53 @@ export function validateModEventUnmute(v: unknown): ValidationResult { return lexicons.validate('tools.ozone.moderation.defs#modEventUnmute', v) } +/** Mute incoming reports from an account */ +export interface ModEventMuteReporter { + comment?: string + /** Indicates how long the account should remain muted. */ + durationInHours: number + [k: string]: unknown +} + +export function isModEventMuteReporter(v: unknown): v is ModEventMuteReporter { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'tools.ozone.moderation.defs#modEventMuteReporter' + ) +} + +export function validateModEventMuteReporter(v: unknown): ValidationResult { + return lexicons.validate( + 'tools.ozone.moderation.defs#modEventMuteReporter', + v, + ) +} + +/** Unmute incoming reports from an account */ +export interface ModEventUnmuteReporter { + /** Describe reasoning behind the reversal. */ + comment?: string + [k: string]: unknown +} + +export function isModEventUnmuteReporter( + v: unknown, +): v is ModEventUnmuteReporter { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'tools.ozone.moderation.defs#modEventUnmuteReporter' + ) +} + +export function validateModEventUnmuteReporter(v: unknown): ValidationResult { + return lexicons.validate( + 'tools.ozone.moderation.defs#modEventUnmuteReporter', + v, + ) +} + /** Keep a log of outgoing email to a user */ export interface ModEventEmail { /** The subject line of the email sent to the user. */ diff --git a/packages/ozone/src/lexicon/types/tools/ozone/moderation/emitEvent.ts b/packages/ozone/src/lexicon/types/tools/ozone/moderation/emitEvent.ts index e3f502bd2eb..0b8737ccafd 100644 --- a/packages/ozone/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +++ b/packages/ozone/src/lexicon/types/tools/ozone/moderation/emitEvent.ts @@ -22,6 +22,9 @@ export interface InputSchema { | ToolsOzoneModerationDefs.ModEventLabel | ToolsOzoneModerationDefs.ModEventReport | ToolsOzoneModerationDefs.ModEventMute + | ToolsOzoneModerationDefs.ModEventUnmute + | ToolsOzoneModerationDefs.ModEventMuteReporter + | ToolsOzoneModerationDefs.ModEventUnmuteReporter | ToolsOzoneModerationDefs.ModEventReverseTakedown | ToolsOzoneModerationDefs.ModEventUnmute | ToolsOzoneModerationDefs.ModEventEmail diff --git a/packages/ozone/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts b/packages/ozone/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts index 188749411fc..aece00e5626 100644 --- a/packages/ozone/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +++ b/packages/ozone/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts @@ -23,6 +23,8 @@ export interface QueryParams { reviewedBefore?: string /** By default, we don't include muted subjects in the results. Set this to true to include them. */ includeMuted?: boolean + /** When set to true, only muted subjects and reporters will be returned. */ + onlyMuted?: boolean /** Specify when fetching subjects in a certain state */ reviewState?: string ignoreSubjects?: string[] diff --git a/packages/ozone/src/mod-service/index.ts b/packages/ozone/src/mod-service/index.ts index 27fba00f5cb..1bb4c9448f5 100644 --- a/packages/ozone/src/mod-service/index.ts +++ b/packages/ozone/src/mod-service/index.ts @@ -18,6 +18,7 @@ import { isModEventTakedown, isModEventEmail, isModEventTag, + isModEventUnmute, } from '../lexicon/types/tools/ozone/moderation/defs' import { RepoRef, RepoBlobRef } from '../lexicon/types/com/atproto/admin/defs' import { @@ -310,6 +311,14 @@ export class ModerationService { } } + // Keep trace of reports that came in while the reporter was in muted stated + if (isModEventReport(event)) { + const isReportingMuted = await this.isReportingMutedForSubject(createdBy) + if (isReportingMuted) { + meta.isReporterMuted = true + } + } + const subjectInfo = subject.info() const modEvent = await this.db.db @@ -668,6 +677,7 @@ export class ModerationService { reportedAfter, reportedBefore, includeMuted, + onlyMuted, ignoreSubjects, sortDirection, lastReviewedBy, @@ -686,6 +696,7 @@ export class ModerationService { reportedAfter?: string reportedBefore?: string includeMuted?: boolean + onlyMuted?: boolean subject?: string ignoreSubjects?: string[] sortDirection: 'asc' | 'desc' @@ -757,6 +768,14 @@ export class ModerationService { ) } + if (onlyMuted) { + builder = builder.where((qb) => + qb + .where('muteUntil', '>', new Date().toISOString()) + .orWhere('muteReportingUntil', '>', new Date().toISOString()), + ) + } + if (tags.length) { builder = builder.where( sql`${ref('moderation_subject_status.tags')} ?| array[${sql.join( @@ -817,6 +836,20 @@ export class ModerationService { return result ?? null } + // This is used to check if the reporter of an incoming report is muted from reporting + // so we want to make sure this look up is as fast as possible + async isReportingMutedForSubject(did: string) { + const result = await this.db.db + .selectFrom('moderation_subject_status') + .where('did', '=', did) + .where('recordPath', '=', '') + .where('muteReportingUntil', '>', new Date().toISOString()) + .select(sql`true`.as('status')) + .executeTakeFirst() + + return !!result + } + async formatAndCreateLabels( uri: string, cid: string | null, diff --git a/packages/ozone/src/mod-service/status.ts b/packages/ozone/src/mod-service/status.ts index 80df140bfe4..939d8d3bea5 100644 --- a/packages/ozone/src/mod-service/status.ts +++ b/packages/ozone/src/mod-service/status.ts @@ -57,6 +57,15 @@ const getSubjectStatusForModerationEvent = ({ suspendUntil: null, lastReviewedAt: createdAt, } + case 'tools.ozone.moderation.defs#modEventUnmuteReporter': + return { + lastReviewedBy: createdBy, + muteReportingUntil: null, + // It's not likely to receive an unmute event that does not already have a status row + // but if it does happen, default to unnecessary + reviewState: defaultReviewState, + lastReviewedAt: createdAt, + } case 'tools.ozone.moderation.defs#modEventUnmute': return { lastReviewedBy: createdBy, @@ -76,6 +85,18 @@ const getSubjectStatusForModerationEvent = ({ ? new Date(Date.now() + durationInHours * HOUR).toISOString() : null, } + case 'tools.ozone.moderation.defs#modEventMuteReporter': + return { + lastReviewedBy: createdBy, + lastReviewedAt: createdAt, + // By default, mute for 24hrs + muteReportingUntil: new Date( + Date.now() + (durationInHours || 24) * HOUR, + ).toISOString(), + // It's not likely to receive a mute event on a subject that does not already have a status row + // but if it does happen, default to unnecessary + reviewState: defaultReviewState, + } case 'tools.ozone.moderation.defs#modEventMute': return { lastReviewedBy: createdBy, @@ -140,6 +161,11 @@ export const adjustModerationSubjectStatus = async ( .selectAll() .executeTakeFirst() + // If reporting is muted for this reporter, we don't want to update the subject status + if (meta?.isReporterMuted) { + return currentStatus || null + } + const isAppealEvent = action === 'tools.ozone.moderation.defs#modEventReport' && meta?.reportType === REASONAPPEAL diff --git a/packages/ozone/src/mod-service/views.ts b/packages/ozone/src/mod-service/views.ts index 64432fc1c96..4a3de9cb0d8 100644 --- a/packages/ozone/src/mod-service/views.ts +++ b/packages/ozone/src/mod-service/views.ts @@ -108,6 +108,7 @@ export class ModerationViews { if ( [ + 'tools.ozone.moderation.defs#modEventMuteReporter', 'tools.ozone.moderation.defs#modEventTakedown', 'tools.ozone.moderation.defs#modEventMute', ].includes(event.action) @@ -157,6 +158,7 @@ export class ModerationViews { eventView.event = { ...eventView.event, reportType: event.meta?.reportType ?? undefined, + isReporterMuted: !!event.meta?.isReporterMuted, } } @@ -500,6 +502,7 @@ export class ModerationViews { lastReportedAt: status.lastReportedAt ?? undefined, lastAppealedAt: status.lastAppealedAt ?? undefined, muteUntil: status.muteUntil ?? undefined, + muteReportingUntil: status.muteReportingUntil ?? undefined, suspendUntil: status.suspendUntil ?? undefined, takendown: status.takendown ?? undefined, appealed: status.appealed ?? undefined, diff --git a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap index 4cf1d3c5012..db3f1a75ecd 100644 --- a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap @@ -7,6 +7,7 @@ Object { "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", "comment": "X", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonMisleading", }, "id": 1, @@ -77,6 +78,7 @@ Array [ "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", "comment": "X", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonSpam", }, "id": 11, @@ -113,6 +115,7 @@ Array [ "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", "comment": "X", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonSpam", }, "id": 5, @@ -135,6 +138,7 @@ Array [ "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", "comment": "X", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonSpam", }, "id": 10, @@ -172,6 +176,7 @@ Array [ "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", "comment": "X", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonSpam", }, "id": 3, diff --git a/packages/ozone/tests/report-muting.test.ts b/packages/ozone/tests/report-muting.test.ts new file mode 100644 index 00000000000..5a2189ba2df --- /dev/null +++ b/packages/ozone/tests/report-muting.test.ts @@ -0,0 +1,100 @@ +import { + TestNetwork, + SeedClient, + basicSeed, + ModeratorClient, +} from '@atproto/dev-env' +import { + ComAtprotoModerationDefs, + ToolsOzoneModerationDefs, +} from '@atproto/api' +import { + REVIEWNONE, + REVIEWOPEN, +} from '../src/lexicon/types/tools/ozone/moderation/defs' + +describe('report-muting', () => { + let network: TestNetwork + let sc: SeedClient + let modClient: ModeratorClient + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'ozone_report_muting', + }) + sc = network.getSeedClient() + modClient = network.ozone.getModClient() + await basicSeed(sc) + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + const assertSubjectStatus = async ( + subject: string, + status?: string, + ): Promise => { + const res = await modClient.queryStatuses({ + subject, + }) + expect(res.subjectStatuses[0]?.reviewState).toEqual(status) + return res.subjectStatuses[0] + } + + it('does not change reviewState when muted reporter reports', async () => { + const bobsPostSubject = { + $type: 'com.atproto.repo.strongRef', + uri: sc.posts[sc.dids.bob][1].ref.uriStr, + cid: sc.posts[sc.dids.bob][1].ref.cidStr, + } + const carolsAccountSubject = { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.carol, + } + + await modClient.emitEvent({ + event: { + $type: 'tools.ozone.moderation.defs#modEventMuteReporter', + durationInHours: 24, + }, + subject: carolsAccountSubject, + }) + await sc.createReport({ + reportedBy: sc.dids.carol, + reasonType: ComAtprotoModerationDefs.REASONMISLEADING, + reason: 'misleading', + subject: bobsPostSubject, + }) + + // Verify that a subject status was not created for bob's post since the reporter was muted + await assertSubjectStatus(bobsPostSubject.uri, undefined) + // Verify, however, that the event was logged + await modClient.queryEvents({ + subject: bobsPostSubject.uri, + }) + + // Verify that reporting mute duration is stored for the reporter + const carolsStatus = await assertSubjectStatus(sc.dids.carol, REVIEWNONE) + expect( + new Date(`${carolsStatus?.muteReportingUntil}`).getTime(), + ).toBeGreaterThan(Date.now()) + + await modClient.emitEvent({ + event: { + $type: 'tools.ozone.moderation.defs#modEventUnmuteReporter', + }, + subject: carolsAccountSubject, + }) + await sc.createReport({ + reportedBy: sc.dids.carol, + reasonType: ComAtprotoModerationDefs.REASONMISLEADING, + reason: 'misleading', + subject: bobsPostSubject, + }) + + // Verify that a subject status was created for bob's post since the reporter was no longer muted + await assertSubjectStatus(bobsPostSubject.uri, REVIEWOPEN) + }) +}) diff --git a/packages/pds/src/db/tables/moderation.ts b/packages/pds/src/db/tables/moderation.ts index 1dddac1ee3d..e8707d4cff4 100644 --- a/packages/pds/src/db/tables/moderation.ts +++ b/packages/pds/src/db/tables/moderation.ts @@ -23,7 +23,13 @@ export interface ModerationAction { | 'tools.ozone.moderation.defs#modEventLabel' | 'tools.ozone.moderation.defs#modEventReport' | 'tools.ozone.moderation.defs#modEventMute' + | 'tools.ozone.moderation.defs#modEventUnmute' + | 'tools.ozone.moderation.defs#modEventMuteReporter' + | 'tools.ozone.moderation.defs#modEventUnmuteReporter' | 'tools.ozone.moderation.defs#modEventReverseTakedown' + | 'tools.ozone.moderation.defs#modEventEmail' + | 'tools.ozone.moderation.defs#modEventResolveAppeal' + | 'tools.ozone.moderation.defs#modEventDivert' subjectType: 'com.atproto.admin.defs#repoRef' | 'com.atproto.repo.strongRef' subjectDid: string subjectUri: string | null diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 2cbb180eb96..f2886955a36 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -8408,6 +8408,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventAcknowledge', 'lex:tools.ozone.moderation.defs#modEventEscalate', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventEmail', 'lex:tools.ozone.moderation.defs#modEventResolveAppeal', 'lex:tools.ozone.moderation.defs#modEventDivert', @@ -8467,6 +8470,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventAcknowledge', 'lex:tools.ozone.moderation.defs#modEventEscalate', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventEmail', 'lex:tools.ozone.moderation.defs#modEventResolveAppeal', 'lex:tools.ozone.moderation.defs#modEventDivert', @@ -8546,6 +8552,10 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + muteReportingUntil: { + type: 'string', + format: 'datetime', + }, lastReviewedBy: { type: 'string', format: 'did', @@ -8669,6 +8679,11 @@ export const schemaDict = { comment: { type: 'string', }, + isReporterMuted: { + type: 'boolean', + description: + "Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject.", + }, reportType: { type: 'ref', ref: 'lex:com.atproto.moderation.defs#reasonType', @@ -8737,6 +8752,30 @@ export const schemaDict = { }, }, }, + modEventMuteReporter: { + type: 'object', + description: 'Mute incoming reports from an account', + required: ['durationInHours'], + properties: { + comment: { + type: 'string', + }, + durationInHours: { + type: 'integer', + description: 'Indicates how long the account should remain muted.', + }, + }, + }, + modEventUnmuteReporter: { + type: 'object', + description: 'Unmute incoming reports from an account', + properties: { + comment: { + type: 'string', + description: 'Describe reasoning behind the reversal.', + }, + }, + }, modEventEmail: { type: 'object', description: 'Keep a log of outgoing email to a user', @@ -9121,6 +9160,9 @@ export const schemaDict = { 'lex:tools.ozone.moderation.defs#modEventLabel', 'lex:tools.ozone.moderation.defs#modEventReport', 'lex:tools.ozone.moderation.defs#modEventMute', + 'lex:tools.ozone.moderation.defs#modEventUnmute', + 'lex:tools.ozone.moderation.defs#modEventMuteReporter', + 'lex:tools.ozone.moderation.defs#modEventUnmuteReporter', 'lex:tools.ozone.moderation.defs#modEventReverseTakedown', 'lex:tools.ozone.moderation.defs#modEventUnmute', 'lex:tools.ozone.moderation.defs#modEventEmail', @@ -9429,6 +9471,11 @@ export const schemaDict = { description: "By default, we don't include muted subjects in the results. Set this to true to include them.", }, + onlyMuted: { + type: 'boolean', + description: + 'When set to true, only muted subjects and reporters will be returned.', + }, reviewState: { type: 'string', description: 'Specify when fetching subjects in a certain state', diff --git a/packages/pds/src/lexicon/types/tools/ozone/moderation/defs.ts b/packages/pds/src/lexicon/types/tools/ozone/moderation/defs.ts index b150d735f6c..d5cfd9f5530 100644 --- a/packages/pds/src/lexicon/types/tools/ozone/moderation/defs.ts +++ b/packages/pds/src/lexicon/types/tools/ozone/moderation/defs.ts @@ -22,6 +22,9 @@ export interface ModEventView { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventUnmute + | ModEventMuteReporter + | ModEventUnmuteReporter | ModEventEmail | ModEventResolveAppeal | ModEventDivert @@ -61,6 +64,9 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventUnmute + | ModEventMuteReporter + | ModEventUnmuteReporter | ModEventEmail | ModEventResolveAppeal | ModEventDivert @@ -105,6 +111,7 @@ export interface SubjectStatusView { /** Sticky comment on the subject. */ comment?: string muteUntil?: string + muteReportingUntil?: string lastReviewedBy?: string lastReviewedAt?: string lastReportedAt?: string @@ -237,6 +244,8 @@ export function validateModEventComment(v: unknown): ValidationResult { /** Report a subject */ export interface ModEventReport { comment?: string + /** Set to true if the reporter was muted from reporting at the time of the event. These reports won't impact the reviewState of the subject. */ + isReporterMuted?: boolean reportType: ComAtprotoModerationDefs.ReasonType [k: string]: unknown } @@ -346,6 +355,53 @@ export function validateModEventUnmute(v: unknown): ValidationResult { return lexicons.validate('tools.ozone.moderation.defs#modEventUnmute', v) } +/** Mute incoming reports from an account */ +export interface ModEventMuteReporter { + comment?: string + /** Indicates how long the account should remain muted. */ + durationInHours: number + [k: string]: unknown +} + +export function isModEventMuteReporter(v: unknown): v is ModEventMuteReporter { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'tools.ozone.moderation.defs#modEventMuteReporter' + ) +} + +export function validateModEventMuteReporter(v: unknown): ValidationResult { + return lexicons.validate( + 'tools.ozone.moderation.defs#modEventMuteReporter', + v, + ) +} + +/** Unmute incoming reports from an account */ +export interface ModEventUnmuteReporter { + /** Describe reasoning behind the reversal. */ + comment?: string + [k: string]: unknown +} + +export function isModEventUnmuteReporter( + v: unknown, +): v is ModEventUnmuteReporter { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'tools.ozone.moderation.defs#modEventUnmuteReporter' + ) +} + +export function validateModEventUnmuteReporter(v: unknown): ValidationResult { + return lexicons.validate( + 'tools.ozone.moderation.defs#modEventUnmuteReporter', + v, + ) +} + /** Keep a log of outgoing email to a user */ export interface ModEventEmail { /** The subject line of the email sent to the user. */ diff --git a/packages/pds/src/lexicon/types/tools/ozone/moderation/emitEvent.ts b/packages/pds/src/lexicon/types/tools/ozone/moderation/emitEvent.ts index e3f502bd2eb..0b8737ccafd 100644 --- a/packages/pds/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +++ b/packages/pds/src/lexicon/types/tools/ozone/moderation/emitEvent.ts @@ -22,6 +22,9 @@ export interface InputSchema { | ToolsOzoneModerationDefs.ModEventLabel | ToolsOzoneModerationDefs.ModEventReport | ToolsOzoneModerationDefs.ModEventMute + | ToolsOzoneModerationDefs.ModEventUnmute + | ToolsOzoneModerationDefs.ModEventMuteReporter + | ToolsOzoneModerationDefs.ModEventUnmuteReporter | ToolsOzoneModerationDefs.ModEventReverseTakedown | ToolsOzoneModerationDefs.ModEventUnmute | ToolsOzoneModerationDefs.ModEventEmail diff --git a/packages/pds/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts b/packages/pds/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts index 188749411fc..aece00e5626 100644 --- a/packages/pds/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts +++ b/packages/pds/src/lexicon/types/tools/ozone/moderation/queryStatuses.ts @@ -23,6 +23,8 @@ export interface QueryParams { reviewedBefore?: string /** By default, we don't include muted subjects in the results. Set this to true to include them. */ includeMuted?: boolean + /** When set to true, only muted subjects and reporters will be returned. */ + onlyMuted?: boolean /** Specify when fetching subjects in a certain state */ reviewState?: string ignoreSubjects?: string[] diff --git a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap index 1ad887fd78d..b19b7645406 100644 --- a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap @@ -50,6 +50,7 @@ Array [ "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", "comment": "impersonation", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonOther", }, "id": 3, @@ -85,6 +86,7 @@ Array [ "creatorHandle": "alice.test", "event": Object { "$type": "tools.ozone.moderation.defs#modEventReport", + "isReporterMuted": false, "reportType": "com.atproto.moderation.defs#reasonSpam", }, "id": 1,