feat: add system messages for federated member add/remove/ownership events

On the receiving instance, federation event processors now write
dm_messages with type='system' for member_added, member_removed, and
owner_changed events and broadcast them via dm_message_created to all
connected local WebSocket clients, matching the behaviour of local
group DM operations.
This commit is contained in:
Jannis Braun
2026-03-27 05:37:27 +01:00
parent 8e589f41fd
commit 2eeca93268
+119
View File
@@ -1766,6 +1766,47 @@ function processMemberAddEvent(
}
if (!bootstrapped) {
// Insert system message for member addition
const actorUser = event.membership.addedBy
? resolveOrCreateReplicatedUser(event.membership.addedBy.homeUserId, event.membership.addedBy.homeInstance, db)
: null;
const actorId = actorUser?.id ?? localUser.id;
const addSysMsgId = generateSnowflake();
const addBaseName = localUser.username?.includes('@') ? localUser.username.split('@')[0] : (localUser.username ?? 'Unknown');
db.insert(schema.dmMessages).values({
id: addSysMsgId,
dmChannelId: channel.id,
userId: actorId,
content: JSON.stringify({
event: 'member_added',
targetUserId: localUser.id,
targetDisplayName: localUser.displayName ?? addBaseName,
}),
type: 'system',
createdAt: Date.now(),
}).run();
connectionManager.sendToDmMembers(channel.id, {
type: 'dm_message_created',
message: {
id: addSysMsgId,
dmChannelId: channel.id,
userId: actorId,
content: JSON.stringify({
event: 'member_added',
targetUserId: localUser.id,
targetDisplayName: localUser.displayName ?? addBaseName,
}),
type: 'system',
createdAt: Date.now(),
user: actorUser ? sanitizeUser(actorUser) : sanitizeUser(localUser),
attachments: [],
embeds: [],
reactions: [],
} as any,
});
// Broadcast to local WebSocket clients
connectionManager.sendToDmMembers(channel.id, {
type: 'dm_member_added',
@@ -1812,6 +1853,44 @@ function processMemberRemoveEvent(
return;
}
// Insert system message for member leaving (before deletion)
const leaveBaseName = localUser.username?.includes('@') ? localUser.username.split('@')[0] : (localUser.username ?? 'Unknown');
const leaveSysMsgId = generateSnowflake();
db.insert(schema.dmMessages).values({
id: leaveSysMsgId,
dmChannelId: channel.id,
userId: localUser.id,
content: JSON.stringify({
event: 'member_removed',
targetUserId: localUser.id,
targetDisplayName: localUser.displayName ?? leaveBaseName,
reason: event.membership?.reason ?? 'leave',
}),
type: 'system',
createdAt: Date.now(),
}).run();
connectionManager.sendToDmMembers(channel.id, {
type: 'dm_message_created',
message: {
id: leaveSysMsgId,
dmChannelId: channel.id,
userId: localUser.id,
content: JSON.stringify({
event: 'member_removed',
targetUserId: localUser.id,
targetDisplayName: localUser.displayName ?? leaveBaseName,
reason: event.membership?.reason ?? 'leave',
}),
type: 'system',
createdAt: Date.now(),
user: sanitizeUser(localUser),
attachments: [],
embeds: [],
reactions: [],
} as any,
});
// Remove member (idempotent)
db.delete(schema.dmMembers)
.where(and(
@@ -1893,6 +1972,46 @@ function processOwnershipTransferEvent(
.where(eq(schema.dmChannels.id, channel.id))
.run();
const prevOwnerLocal = event.ownership.previousOwner
? resolveLocalUser(event.ownership.previousOwner.homeUserId, db)
: null;
const ownerSysMsgId = generateSnowflake();
const newOwnerBaseName = newOwnerLocal?.username?.includes('@') ? newOwnerLocal.username.split('@')[0] : (newOwnerLocal?.username ?? 'Unknown');
const prevOwnerId = prevOwnerLocal?.id ?? channel.ownerId ?? 'system';
db.insert(schema.dmMessages).values({
id: ownerSysMsgId,
dmChannelId: channel.id,
userId: prevOwnerId,
content: JSON.stringify({
event: 'owner_changed',
newOwnerId: newOwnerLocal?.id ?? channel.ownerId,
newOwnerDisplayName: newOwnerLocal?.displayName ?? newOwnerBaseName,
}),
type: 'system',
createdAt: Date.now(),
}).run();
connectionManager.sendToDmMembers(channel.id, {
type: 'dm_message_created',
message: {
id: ownerSysMsgId,
dmChannelId: channel.id,
userId: prevOwnerId,
content: JSON.stringify({
event: 'owner_changed',
newOwnerId: newOwnerLocal?.id ?? channel.ownerId,
newOwnerDisplayName: newOwnerLocal?.displayName ?? newOwnerBaseName,
}),
type: 'system',
createdAt: Date.now(),
user: prevOwnerLocal ? sanitizeUser(prevOwnerLocal) : undefined,
attachments: [],
embeds: [],
reactions: [],
} as any,
});
accepted.push(event.messageId);
}