diff --git a/packages/core/src/events/eventBase.ts b/packages/core/src/events/eventBase.ts index f37f3719a..93916c355 100644 --- a/packages/core/src/events/eventBase.ts +++ b/packages/core/src/events/eventBase.ts @@ -39,8 +39,8 @@ export type RedactedEvent = EventBase & { export const isRedactedEvent = ( event: Pdu, -): event is PduForType<'m.room.redaction'> & {} => { - return event.type === 'm.room.redaction'; +): event is PduForType<'m.room.redaction'> => { + return event.type === 'm.room.redaction' && 'redacts' in event; }; export interface Events {} diff --git a/packages/core/src/events/m.room.redaction.spec.ts b/packages/core/src/events/m.room.redaction.spec.ts index db6ab78ff..34475b853 100644 --- a/packages/core/src/events/m.room.redaction.spec.ts +++ b/packages/core/src/events/m.room.redaction.spec.ts @@ -70,9 +70,9 @@ test('redactionEvent', async () => { prev_events: ['$8ftnUd9WTPTQGbdPgfOPea8bOEQ21qPvbcGqeOApQxA'], depth: 4, content: { - redacts: '$8ftnUd9WTPTQGbdPgfOPea8bOEQ21qPvbcGqeOApQxA', reason: 'Inappropriate content', }, + redacts: '$8ftnUd9WTPTQGbdPgfOPea8bOEQ21qPvbcGqeOApQxA', origin: 'rc1', ts: 1747837631863, }); diff --git a/packages/core/src/events/m.room.redaction.ts b/packages/core/src/events/m.room.redaction.ts index 0389978fc..61b23649a 100644 --- a/packages/core/src/events/m.room.redaction.ts +++ b/packages/core/src/events/m.room.redaction.ts @@ -41,6 +41,7 @@ export const redactionEvent = ({ prev_events, depth, content, + redacts, origin, ts = Date.now(), unsigned, @@ -51,15 +52,14 @@ export const redactionEvent = ({ prev_events: EventID[]; depth: number; content: { - redacts: EventID; reason?: string; }; + redacts: EventID; origin?: string; ts?: number; unsigned?: { age_ts?: number }; }): RedactionEvent => { // Extract redacts from content - it must be at top level only - const { redacts } = content; const { reason } = content; const baseEvent = createEventBase('m.room.redaction', { diff --git a/packages/federation-sdk/src/services/message.service.ts b/packages/federation-sdk/src/services/message.service.ts index 3a8e4c3b1..d113cdab8 100644 --- a/packages/federation-sdk/src/services/message.service.ts +++ b/packages/federation-sdk/src/services/message.service.ts @@ -355,8 +355,8 @@ export class MessageService { type: 'm.room.redaction', content: { reason: 'Unsetting reaction', - redacts: eventIdReactedTo, }, + redacts: eventIdReactedTo, room_id: roomId, auth_events: [], depth: 0, @@ -422,7 +422,6 @@ export class MessageService { async redactMessage( roomId: string, eventIdToRedact: EventID, - senderUserId: string, ): Promise { const isTombstoned = await this.roomService.isRoomTombstoned(roomId); if (isTombstoned) { @@ -434,20 +433,25 @@ export class MessageService { const roomInfo = await this.stateService.getRoomInformation(roomId); + const senderUserId = await this.eventService.getEventById(eventIdToRedact); + if (!senderUserId?.event.sender) { + throw new Error(`Sender user ID not found for event ${eventIdToRedact}`); + } + const redactionEvent = await this.stateService.buildEvent<'m.room.redaction'>( { type: 'm.room.redaction', content: { reason: `Deleting message: ${eventIdToRedact}`, - redacts: eventIdToRedact, }, + redacts: eventIdToRedact, room_id: roomId, auth_events: [], depth: 0, prev_events: [], origin_server_ts: Date.now(), - sender: senderUserId, + sender: senderUserId.event.sender, }, roomInfo.room_version, ); diff --git a/packages/federation-sdk/src/services/room.service.ts b/packages/federation-sdk/src/services/room.service.ts index 222e87132..8e00948eb 100644 --- a/packages/federation-sdk/src/services/room.service.ts +++ b/packages/federation-sdk/src/services/room.service.ts @@ -39,6 +39,7 @@ import { RoomRepository } from '../repositories/room.repository'; import { ConfigService } from './config.service'; import { EventService } from './event.service'; +import { EventEmitterService } from './event-emitter.service'; import { InviteService } from './invite.service'; import { StateService } from './state.service'; @@ -52,6 +53,7 @@ export class RoomService { private readonly federationService: FederationService, private readonly stateService: StateService, private readonly inviteService: InviteService, + private readonly eventEmitterService: EventEmitterService, ) {} private validatePowerLevelChange( @@ -469,7 +471,11 @@ export class RoomService { // Ensure critical auth events were found if (!createAuthResult || !powerLevelsAuthResult || !memberAuthResult) { logger.error( - `Critical auth events missing for power level update. Create: ${createAuthResult?._id ?? 'missing'}, PowerLevels: ${powerLevelsAuthResult?._id ?? 'missing'}, Member: ${memberAuthResult?._id ?? 'missing'}`, + `Critical auth events missing for power level update. Create: ${ + createAuthResult?._id ?? 'missing' + }, PowerLevels: ${powerLevelsAuthResult?._id ?? 'missing'}, Member: ${ + memberAuthResult?._id ?? 'missing' + }`, ); throw new HttpException( 'Internal server error: Missing auth events for power level update.', @@ -617,7 +623,9 @@ export class RoomService { reason?: string, ): Promise { logger.info( - `User ${senderId} kicking user ${kickedUserId} from room ${roomId}. Reason: ${reason || 'No reason specified'}`, + `User ${senderId} kicking user ${kickedUserId} from room ${roomId}. Reason: ${ + reason || 'No reason specified' + }`, ); const roomInfo = await this.stateService.getRoomInformation(roomId); @@ -696,7 +704,9 @@ export class RoomService { reason?: string, ): Promise { logger.info( - `User ${senderId} banning user ${bannedUserId} from room ${roomId}. Reason: ${reason || 'No reason specified'}`, + `User ${senderId} banning user ${bannedUserId} from room ${roomId}. Reason: ${ + reason || 'No reason specified' + }`, ); const roomInfo = await this.stateService.getRoomInformation(roomId); @@ -809,6 +819,15 @@ export class RoomService { await stateService.persistStateEvent(membershipEvent); + this.eventEmitterService.emit('homeserver.matrix.membership', { + room_id: roomId, + state_key: userId, + content: { membership: 'join' }, + sender: userId, + event_id: membershipEvent.eventId, + origin_server_ts: Date.now(), + }); + if (membershipEvent.rejected) { throw new Error(membershipEvent.rejectedReason); } @@ -909,7 +928,11 @@ export class RoomService { if (!authEvent) { for (const stateEvent of eventMap.keys()) { console.log( - `${stateEvent} -> ${JSON.stringify(eventMap.get(stateEvent)?.event, null, 2)}`, + `${stateEvent} -> ${JSON.stringify( + eventMap.get(stateEvent)?.event, + null, + 2, + )}`, ); } throw new Error(`Auth event ${authEventId} not found`); @@ -936,11 +959,9 @@ export class RoomService { // persist as normal logger.info( - `Persisting state event after auth events have been persisted, ${event.eventId}, ${JSON.stringify( - event.event, - null, - 2, - )}`, + `Persisting state event after auth events have been persisted, ${ + event.eventId + }, ${JSON.stringify(event.event, null, 2)}`, ); const eventToPersist = copyEvent(event); @@ -983,7 +1004,10 @@ export class RoomService { const state = await stateService.getFullRoomState(roomId); logger.info( - `State before join event has been persisted, ${state.keys().toArray().join(', ')}`, + `State before join event has been persisted, ${state + .keys() + .toArray() + .join(', ')}`, ); // try to persist the join event now, should succeed with state in place diff --git a/packages/federation-sdk/src/services/staging-area.service.ts b/packages/federation-sdk/src/services/staging-area.service.ts index 25cddf4ab..6fd2f154f 100644 --- a/packages/federation-sdk/src/services/staging-area.service.ts +++ b/packages/federation-sdk/src/services/staging-area.service.ts @@ -229,7 +229,7 @@ export class StagingAreaService { room_id: roomId, sender: event.event.sender, origin_server_ts: event.event.origin_server_ts, - redacts: event.event.content.redacts, + redacts: event.event.redacts, content: { reason: event.event.content?.reason as string | undefined, }, diff --git a/packages/homeserver/src/controllers/internal/message.controller.ts b/packages/homeserver/src/controllers/internal/message.controller.ts index 75a5b51c3..c3317f7d5 100644 --- a/packages/homeserver/src/controllers/internal/message.controller.ts +++ b/packages/homeserver/src/controllers/internal/message.controller.ts @@ -152,11 +152,10 @@ export const internalMessagePlugin = (app: Elysia) => { params, body, }): Promise => { - const { roomId, senderUserId } = body; + const { roomId } = body; const eventId = await messageService.redactMessage( roomId, params.messageId as EventID, - senderUserId, ); return { diff --git a/packages/homeserver/src/dtos/internal/message.dto.ts b/packages/homeserver/src/dtos/internal/message.dto.ts index 9e00d9ab7..683157854 100644 --- a/packages/homeserver/src/dtos/internal/message.dto.ts +++ b/packages/homeserver/src/dtos/internal/message.dto.ts @@ -59,7 +59,6 @@ export const InternalRedactMessageParamsDto = t.Object({ export const InternalRedactMessageBodyDto = t.Object({ roomId: RoomIdDto, - senderUserId: UsernameDto, }); export const InternalRedactMessageResponseDto = InternalMessageResponseDto; diff --git a/packages/room/src/types/v3-11.ts b/packages/room/src/types/v3-11.ts index 8cc078539..60dbdbc56 100644 --- a/packages/room/src/types/v3-11.ts +++ b/packages/room/src/types/v3-11.ts @@ -197,7 +197,6 @@ export type PduRoomTopicEventContent = z.infer< export const PduRoomRedactionContentSchema = z.object({ reason: z.string().optional(), - redacts: eventIdSchema.describe('event id'), }); export type PduRoomRedactionContent = z.infer< @@ -692,6 +691,7 @@ const EventPduTypeRoomRedaction = z.object({ ...PduNoContentTimelineEventSchema, type: z.literal('m.room.redaction'), content: PduRoomRedactionContentSchema, + redacts: eventIdSchema.describe('event id'), }); export const PduSchema = z.discriminatedUnion('type', [