diff --git a/src/draupnirfactory/DraupnirProtectedRoomsSet.ts b/src/draupnirfactory/DraupnirProtectedRoomsSet.ts index 498605d2..070fc221 100644 --- a/src/draupnirfactory/DraupnirProtectedRoomsSet.ts +++ b/src/draupnirfactory/DraupnirProtectedRoomsSet.ts @@ -11,6 +11,7 @@ import { ActionResult, ClientPlatform, + DefaultMixinExtractor, LoggableConfigTracker, Logger, MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE, @@ -219,6 +220,7 @@ export async function makeProtectedRoomsSet( protectedRoomsManager.ok, protectionsConfig.ok, userID, + DefaultMixinExtractor, makeHandleMissingProtectionPermissions( clientPlatform.toRoomMessageSender(), managementRoom.toRoomIDOrAlias() diff --git a/src/protections/MentionLimitProtection.tsx b/src/protections/MentionLimitProtection.tsx index 0eda0095..83bc20f5 100644 --- a/src/protections/MentionLimitProtection.tsx +++ b/src/protections/MentionLimitProtection.tsx @@ -5,16 +5,20 @@ import { AbstractProtection, + ContentMixins, EDStatic, EventConsequences, + EventWithMixins, Logger, - MediaMixinTypes, + MentionsMixin, + MentionsMixinDescription, + NewContentMixinDescription, Ok, ProtectedRoomsSet, Protection, ProtectionDescription, + RoomMessageBodyMixinDescription, RoomMessageSender, - SafeMediaEvent, Task, UserConsequences, describeProtection, @@ -36,29 +40,57 @@ import { Result } from "@gnuxie/typescript-result"; const log = new Logger("MentionLimitProtection"); -export function isContainingMentionsOverLimit( - event: SafeMediaEvent, +function isMentionsMixinOverLimit( + mentionsMixin: MentionsMixin, + maxMentions: number +): boolean { + return mentionsMixin.user_ids.length > maxMentions; +} + +function isContentContaningMentionsOverLimit( + content: ContentMixins, maxMentions: number, checkBody: boolean ): boolean { - const bodyMedia = event.media.filter( - (mixin) => mixin.mixinType === MediaMixinTypes.Body - ); - const mentionMedia = event.media.filter( - (mixin) => mixin.mixinType === MediaMixinTypes.Mentions - ); - const isOverLimit = (user_ids: string[]): boolean => - user_ids.length > maxMentions; - if (mentionMedia.some((mixin) => isOverLimit(mixin.user_ids))) { - return true; - } + const mentionMixin = content.findMixin(MentionsMixinDescription); if ( - checkBody && - bodyMedia.some((mixin) => mixin.body.split("@").length - 1 > maxMentions) + mentionMixin?.isErroneous === false && + isMentionsMixinOverLimit(mentionMixin, maxMentions) ) { return true; } - return false; + if (!checkBody) { + return false; + } + const bodyMixin = content.findMixin(RoomMessageBodyMixinDescription); + if (bodyMixin === undefined || bodyMixin.isErroneous) { + return false; + } + return bodyMixin.body.split("@").length - 1 > maxMentions; +} + +export function isContainingMentionsOverLimit( + event: EventWithMixins, + maxMentions: number, + checkBody: boolean +): boolean { + const isTopContentOverLimit = isContentContaningMentionsOverLimit( + event, + maxMentions, + checkBody + ); + if (isTopContentOverLimit) { + return true; + } + const newContentMixin = event.findMixin(NewContentMixinDescription); + if (newContentMixin === undefined || newContentMixin.isErroneous) { + return false; + } + return isContentContaningMentionsOverLimit( + newContentMixin, + maxMentions, + checkBody + ); } const MentionLimitProtectionSettings = Type.Object( @@ -121,8 +153,11 @@ export class MentionLimitProtection this.warningText = settings.warningText; this.includeLegacymentions = settings.includeLegacyMentions; } - public handleTimelineMedia(_room: MatrixRoomID, event: SafeMediaEvent): void { - if (event.sender === this.protectedRoomsSet.userID) { + public handleTimelineEventMixins( + _room: MatrixRoomID, + event: EventWithMixins + ): void { + if (event.sourceEvent.sender === this.protectedRoomsSet.userID) { return; } if ( @@ -139,17 +174,24 @@ export class MentionLimitProtection } public async handleEventOverLimit( - event: SafeMediaEvent + event: EventWithMixins ): Promise> { - const infractions = this.consequenceBucket.getTokenCount(event.sender); + const sourceEvent = event.sourceEvent; + const infractions = this.consequenceBucket.getTokenCount( + sourceEvent.sender + ); if (infractions > 0) { const userResult = await this.userConsequences.consequenceForUserInRoom( - event.room_id, - event.sender, + sourceEvent.room_id, + sourceEvent.sender, this.warningText ); if (isError(userResult)) { - log.error("Failed to ban the user", event.sender, userResult.error); + log.error( + "Failed to ban the user", + sourceEvent.sender, + userResult.error + ); } // fall through to the event consequence on purpose so we redact the event too. } else { @@ -157,21 +199,22 @@ export class MentionLimitProtection void Task( sendMatrixEventsFromDeadDocument( this.roomMessageSender, - event.room_id, + sourceEvent.room_id, - {renderMentionPill(event.sender, event.sender)} {this.warningText} + {renderMentionPill(sourceEvent.sender, sourceEvent.sender)}{" "} + {this.warningText} , - { replyToEvent: event.sourceEvent } + { replyToEvent: sourceEvent } ), { log, } ); } - this.consequenceBucket.addToken(event.sender); + this.consequenceBucket.addToken(sourceEvent.sender); return await this.eventConsequences.consequenceForEvent( - event.room_id, - event.event_id, + sourceEvent.room_id, + sourceEvent.event_id, this.warningText ); } diff --git a/test/unit/protections/MentionLimitProtectionTest.ts b/test/unit/protections/MentionLimitProtectionTest.ts index 02d953e9..595d1c86 100644 --- a/test/unit/protections/MentionLimitProtectionTest.ts +++ b/test/unit/protections/MentionLimitProtectionTest.ts @@ -4,11 +4,11 @@ // SPDX-License-Identifier: Apache-2.0 import { - extractSafeMediaEvent, + DefaultMixinExtractor, + EventWithMixins, randomRoomID, randomUserID, RoomEvent, - SafeMediaEvent, } from "matrix-protection-suite"; import { isContainingMentionsOverLimit } from "../../../src/protections/MentionLimitProtection"; import expect from "expect"; @@ -17,8 +17,8 @@ function messageEvent(content: { body?: string; formatted_body?: string; "m.mentions"?: { user_ids: string[] }; -}): SafeMediaEvent { - return extractSafeMediaEvent({ +}): EventWithMixins { + return DefaultMixinExtractor.parseEvent({ content, type: "m.room.message", sender: randomUserID(),