From 925326e2d9807d55ea131bc4ffd7d379b0f04dd7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 13 May 2026 12:15:51 -0600 Subject: [PATCH] Remove MSC3215 (Report to Moderators) labs feature T&S does not believe this is used, and intends to replace it during Reporting v2 anyway. Original MSC: https://github.com/matrix-org/matrix-spec-proposals/pull/3215 New (WIP) MSC: https://github.com/matrix-org/matrix-spec-proposals/pull/4468 (This PR/commit doesn't add MSC4468 support because it's unclear if we should use MSC4468 directly or via another endpoint like MSC4457) [MSC4457]: https://github.com/matrix-org/matrix-spec-proposals/pull/4457 --- .../views/dialogs/ReportEventDialog.tsx | 319 +----------------- apps/web/src/i18n/strings/en_EN.json | 20 +- apps/web/src/settings/Settings.tsx | 10 - docs/labs.md | 5 - 4 files changed, 13 insertions(+), 341 deletions(-) diff --git a/apps/web/src/components/views/dialogs/ReportEventDialog.tsx b/apps/web/src/components/views/dialogs/ReportEventDialog.tsx index 7c840c56ea..d9391b7355 100644 --- a/apps/web/src/components/views/dialogs/ReportEventDialog.tsx +++ b/apps/web/src/components/views/dialogs/ReportEventDialog.tsx @@ -1,4 +1,5 @@ /* +Copyright 2026 Element Creations Ltd. Copyright 2024 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> @@ -11,25 +12,16 @@ import React, { type JSX, type ChangeEvent } from "react"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { _t, UserFriendlyError } from "../../../languageHandler"; -import { ensureDMExists } from "../../../createRoom"; +import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import SdkConfig from "../../../SdkConfig"; import Markdown from "../../../Markdown"; -import SettingsStore from "../../../settings/SettingsStore"; -import StyledRadioButton from "../elements/StyledRadioButton"; import BaseDialog from "./BaseDialog"; import DialogButtons from "../elements/DialogButtons"; import Field from "../elements/Field"; import Spinner from "../elements/Spinner"; import LabelledCheckbox from "../elements/LabelledCheckbox"; -declare module "matrix-js-sdk/src/types" { - interface TimelineEvents { - [ABUSE_EVENT_TYPE]: AbuseEventContent; - } -} - interface IProps { mxEvent: MatrixEvent; onFinished(report?: boolean): void; @@ -40,8 +32,6 @@ interface IState { reason: string; busy: boolean; err?: string; - // If we know it, the nature of the abuse, as specified by MSC3215. - nature?: ExtendedNature; ignoreUserToo: boolean; // if true, user will be ignored/blocked on submit /* * Whether the room is encrypted. @@ -49,148 +39,18 @@ interface IState { isRoomEncrypted: boolean; } -const MODERATED_BY_STATE_EVENT_TYPE = [ - "org.matrix.msc3215.room.moderation.moderated_by", - /** - * Unprefixed state event. Not ready for prime time. - * - * "m.room.moderation.moderated_by" - */ -]; - -export const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report"; - -interface AbuseEventContent { - event_id: string; - room_id: string; - moderated_by_id: string; - nature?: ExtendedNature; - reporter: string; - comment: string; -} - -// Standard abuse natures. -enum Nature { - Disagreement = "org.matrix.msc3215.abuse.nature.disagreement", - Toxic = "org.matrix.msc3215.abuse.nature.toxic", - Illegal = "org.matrix.msc3215.abuse.nature.illegal", - Spam = "org.matrix.msc3215.abuse.nature.spam", - Other = "org.matrix.msc3215.abuse.nature.other", -} - -enum NonStandardValue { - // Non-standard abuse nature. - // It should never leave the client - we use it to fallback to - // server-wide abuse reporting. - Admin = "non-standard.abuse.nature.admin", -} - -type ExtendedNature = Nature | NonStandardValue; - -type Moderation = { - // The id of the moderation room. - moderationRoomId: string; - // The id of the bot in charge of forwarding abuse reports to the moderation room. - moderationBotUserId: string; -}; /* * A dialog for reporting an event. - * - * The actual content of the dialog will depend on two things: - * - * 1. Is `feature_report_to_moderators` enabled? - * 2. Does the room support moderation as per MSC3215, i.e. is there - * a well-formed state event `m.room.moderation.moderated_by` - * /`org.matrix.msc3215.room.moderation.moderated_by`? */ export default class ReportEventDialog extends React.Component { - // If the room supports moderation, the moderation information. - private moderation?: Moderation; - public constructor(props: IProps) { super(props); - let moderatedByRoomId: string | null = null; - let moderatedByUserId: string | null = null; - - if (SettingsStore.getValue("feature_report_to_moderators")) { - // The client supports reporting to moderators. - // Does the room support it, too? - - // Extract state events to determine whether we should display - const client = MatrixClientPeg.safeGet(); - const room = client.getRoom(props.mxEvent.getRoomId()); - - for (const stateEventType of MODERATED_BY_STATE_EVENT_TYPE) { - const stateEvent = room?.currentState.getStateEvents(stateEventType, stateEventType); - if (!stateEvent) { - continue; - } - if (Array.isArray(stateEvent)) { - // Internal error. - throw new TypeError( - `getStateEvents(${stateEventType}, ${stateEventType}) ` + - "should return at most one state event", - ); - } - const event = stateEvent.event; - if (!("content" in event) || typeof event["content"] != "object") { - // The room is improperly configured. - // Display this debug message for the sake of moderators. - console.debug( - "Moderation error", - "state event", - stateEventType, - "should have an object field `content`, got", - event, - ); - continue; - } - const content = event["content"]; - if (!("room_id" in content) || typeof content["room_id"] != "string") { - // The room is improperly configured. - // Display this debug message for the sake of moderators. - console.debug( - "Moderation error", - "state event", - stateEventType, - "should have a string field `content.room_id`, got", - event, - ); - continue; - } - if (!("user_id" in content) || typeof content["user_id"] != "string") { - // The room is improperly configured. - // Display this debug message for the sake of moderators. - console.debug( - "Moderation error", - "state event", - stateEventType, - "should have a string field `content.user_id`, got", - event, - ); - continue; - } - moderatedByRoomId = content["room_id"]; - moderatedByUserId = content["user_id"]; - } - - if (moderatedByRoomId && moderatedByUserId) { - // The room supports moderation. - this.moderation = { - moderationRoomId: moderatedByRoomId, - moderationBotUserId: moderatedByUserId, - }; - } - } - this.state = { // A free-form text describing the abuse. reason: "", busy: false, err: undefined, - // If specified, the nature of the abuse, as specified by MSC3215. - nature: undefined, ignoreUserToo: false, // default false, for now. Could easily be argued as default true isRoomEncrypted: false, // async, will be set later }; @@ -215,11 +75,6 @@ export default class ReportEventDialog extends React.Component { this.setState({ reason }); }; - // The user has clicked on a nature. - private onNatureChosen = (e: React.FormEvent): void => { - this.setState({ nature: e.currentTarget.value as ExtendedNature }); - }; - // The user has clicked "cancel". private onCancel = (): void => { this.props.onFinished(false); @@ -229,28 +84,13 @@ export default class ReportEventDialog extends React.Component { private onSubmit = async (): Promise => { let reason = this.state.reason || ""; reason = reason.trim(); - if (this.moderation) { - // This room supports moderation. - // We need a nature. - // If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`. - if ( - !this.state.nature || - ((this.state.nature == Nature.Other || this.state.nature == NonStandardValue.Admin) && !reason) - ) { - this.setState({ - err: _t("report_content|missing_reason"), - }); - return; - } - } else { - // This room does not support moderation. - // We need a `reason`. - if (!reason) { - this.setState({ - err: _t("report_content|missing_reason"), - }); - return; - } + + // Reasons are required on the API, even if unhelpful like "." + if (!reason) { + this.setState({ + err: _t("report_content|missing_reason"), + }); + return; } this.setState({ @@ -261,28 +101,10 @@ export default class ReportEventDialog extends React.Component { try { const client = MatrixClientPeg.safeGet(); const ev = this.props.mxEvent; - if (this.moderation && this.state.nature !== NonStandardValue.Admin) { - const nature = this.state.nature; - // Report to moderators through to the dedicated bot, - // as configured in the room's state events. - const dmRoomId = await ensureDMExists(client, this.moderation.moderationBotUserId); - if (!dmRoomId) { - throw new UserFriendlyError("report_content|error_create_room_moderation_bot"); - } - - await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, { - event_id: ev.getId()!, - room_id: ev.getRoomId()!, - moderated_by_id: this.moderation.moderationRoomId, - nature, - reporter: client.getUserId()!, - comment: this.state.reason.trim(), - } satisfies AbuseEventContent); - } else { - // Report to homeserver admin through the dedicated Matrix API. - await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim()); - } + // Report to homeserver admin through the dedicated Matrix API. We hardcode the "score" because it's + // not actually used by anything. + await client.reportEvent(ev.getRoomId()!, ev.getId()!, -100, this.state.reason.trim()); // if the user should also be ignored, do that if (this.state.ignoreUserToo) { @@ -331,123 +153,6 @@ export default class ReportEventDialog extends React.Component { adminMessage =

; } - if (this.moderation) { - // Display report-to-moderator dialog. - // We let the user pick a nature. - const homeServerName = SdkConfig.get("validated_server_config")!.hsName; - let subtitle: string; - switch (this.state.nature) { - case Nature.Disagreement: - subtitle = _t("report_content|nature_disagreement"); - break; - case Nature.Toxic: - subtitle = _t("report_content|nature_toxic"); - break; - case Nature.Illegal: - subtitle = _t("report_content|nature_illegal"); - break; - case Nature.Spam: - subtitle = _t("report_content|nature_spam"); - break; - case NonStandardValue.Admin: - if (this.state.isRoomEncrypted) { - subtitle = _t("report_content|nature_nonstandard_admin_encrypted", { - homeserver: homeServerName, - }); - } else { - subtitle = _t("report_content|nature_nonstandard_admin", { homeserver: homeServerName }); - } - break; - case Nature.Other: - subtitle = _t("report_content|nature_other"); - break; - default: - subtitle = _t("report_content|nature"); - break; - } - - return ( - -

- - {_t("report_content|disagree")} - - - {_t("report_content|toxic_behaviour")} - - - {_t("report_content|illegal_content")} - - - {_t("report_content|spam_or_propaganda")} - - - {_t("report_content|report_entire_room")} - - - {_t("report_content|other_label")} - -

{subtitle}

- - {progress} - {error} - {ignoreUserCheckbox} -
- - - ); - } - // Report to homeserver admin. - // Currently, the API does not support natures. return (