/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import {AttachmentDecayService} from '~/attachment/AttachmentDecayService';
import {type ChannelID, channelIdToUserId, type MessageID, type UserID} from '~/BrandedTypes';
import {ChannelTypes, type GatewayDispatchEvent} from '~/Constants';
import {mapMessageToResponse} from '~/channel/ChannelModel';
import type {IChannelRepositoryAggregate} from '~/channel/repositories/IChannelRepositoryAggregate';
import type {IGatewayService} from '~/infrastructure/IGatewayService';
import type {IMediaService} from '~/infrastructure/IMediaService';
import type {UserCacheService} from '~/infrastructure/UserCacheService';
import type {Channel, Message} from '~/Models';
import type {RequestCache} from '~/middleware/RequestCacheMiddleware';
import {collectMessageAttachments} from './MessageHelpers';
export class MessageDispatchService {
constructor(
private gatewayService: IGatewayService,
private userCacheService: UserCacheService,
private mediaService: IMediaService,
private channelRepository: IChannelRepositoryAggregate,
private attachmentDecayService: AttachmentDecayService = new AttachmentDecayService(),
) {}
async dispatchEvent(params: {channel: Channel; event: GatewayDispatchEvent; data: unknown}): Promise {
const {channel, event, data} = params;
if (channel.type === ChannelTypes.DM_PERSONAL_NOTES) {
return this.gatewayService.dispatchPresence({
userId: channelIdToUserId(channel.id),
event,
data,
});
}
if (channel.guildId) {
return this.gatewayService.dispatchGuild({guildId: channel.guildId, event, data});
}
await Promise.all(
Array.from(channel.recipientIds).map((recipientId) =>
this.gatewayService.dispatchPresence({userId: recipientId, event, data}),
),
);
}
async dispatchMessageCreate({
channel,
message,
requestCache,
currentUserId,
nonce,
tts,
}: {
channel: Channel;
message: Message;
requestCache: RequestCache;
currentUserId?: UserID;
nonce?: string;
tts?: boolean;
}): Promise {
const messageAttachments = collectMessageAttachments(message);
const attachmentDecayMap =
messageAttachments.length > 0
? await this.attachmentDecayService.fetchMetadata(messageAttachments.map((att) => ({attachmentId: att.id})))
: undefined;
const messageResponse = await mapMessageToResponse({
message,
currentUserId,
nonce,
tts,
userCacheService: this.userCacheService,
requestCache,
mediaService: this.mediaService,
attachmentDecayMap,
getReferencedMessage: (channelId: ChannelID, messageId: MessageID) =>
this.channelRepository.messages.getMessage(channelId, messageId),
});
await this.dispatchEvent({
channel,
event: 'MESSAGE_CREATE',
data: {...messageResponse, channel_type: channel.type},
});
}
async dispatchMessageUpdate({
channel,
message,
requestCache,
currentUserId,
}: {
channel: Channel;
message: Message;
requestCache: RequestCache;
currentUserId?: UserID;
}): Promise {
const messageAttachments = collectMessageAttachments(message);
const attachmentDecayMap =
messageAttachments.length > 0
? await this.attachmentDecayService.fetchMetadata(messageAttachments.map((att) => ({attachmentId: att.id})))
: undefined;
const messageResponse = await mapMessageToResponse({
message,
currentUserId,
userCacheService: this.userCacheService,
requestCache,
mediaService: this.mediaService,
attachmentDecayMap,
getReferencedMessage: (channelId: ChannelID, messageId: MessageID) =>
this.channelRepository.messages.getMessage(channelId, messageId),
});
await this.dispatchEvent({
channel,
event: 'MESSAGE_UPDATE',
data: messageResponse,
});
}
async dispatchMessageDelete({
channel,
messageId,
message,
}: {
channel: Channel;
messageId: MessageID;
message: Message;
}): Promise {
await this.dispatchEvent({
channel,
event: 'MESSAGE_DELETE',
data: {
channel_id: channel.id.toString(),
id: messageId.toString(),
content: message.content,
author_id: message.authorId?.toString(),
},
});
}
async dispatchMessageDeleteBulk({
channel,
messageIds,
}: {
channel: Channel;
messageIds: Array;
}): Promise {
await this.dispatchEvent({
channel,
event: 'MESSAGE_DELETE_BULK',
data: {
channel_id: channel.id.toString(),
ids: messageIds.map((id) => id.toString()),
},
});
}
}