mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
183 lines
6.5 KiB
TypeScript
183 lines
6.5 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
import type {ChannelID, UserID} from '@app/api/BrandedTypes';
|
|
import {createMessageID} from '@app/api/BrandedTypes';
|
|
import type {ChannelRepository} from '@app/api/channel/ChannelRepository';
|
|
import type {ChannelUtilsService} from '@app/api/channel/services/channel_data/ChannelUtilsService';
|
|
import type {MessagePersistenceService} from '@app/api/channel/services/message/MessagePersistenceService';
|
|
import type {ChannelRow} from '@app/api/database/types/ChannelTypes';
|
|
import type {AvatarService} from '@app/api/infrastructure/AvatarService';
|
|
import {contentModerationService} from '@app/api/infrastructure/ContentModerationService';
|
|
import type {ISnowflakeService} from '@app/api/infrastructure/ISnowflakeService';
|
|
import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware';
|
|
import type {Channel} from '@app/api/models/Channel';
|
|
import type {UserRepository} from '@app/api/user/repositories/UserRepository';
|
|
import {canUserAccessNsfwContent} from '@app/api/utils/AgeUtils';
|
|
import {ChannelTypes, MessageTypes} from '@fluxer/constants/src/ChannelConstants';
|
|
import {GroupDmMatureContentIneligibleError} from '@fluxer/errors/src/domains/channel/GroupDmMatureContentIneligibleError';
|
|
import {InvalidChannelTypeError} from '@fluxer/errors/src/domains/channel/InvalidChannelTypeError';
|
|
import {UnknownChannelError} from '@fluxer/errors/src/domains/channel/UnknownChannelError';
|
|
import {MissingAccessError} from '@fluxer/errors/src/domains/core/MissingAccessError';
|
|
import {MissingPermissionsError} from '@fluxer/errors/src/domains/core/MissingPermissionsError';
|
|
import {CannotTransferOwnershipToBotError} from '@fluxer/errors/src/domains/guild/CannotTransferOwnershipToBotError';
|
|
import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';
|
|
|
|
export class GroupDmUpdateService {
|
|
constructor(
|
|
private channelRepository: ChannelRepository,
|
|
private userRepository: UserRepository,
|
|
private avatarService: AvatarService,
|
|
private snowflakeService: ISnowflakeService,
|
|
private channelUtilsService: ChannelUtilsService,
|
|
private messagePersistenceService: MessagePersistenceService,
|
|
) {}
|
|
|
|
async updateGroupDmChannel({
|
|
userId,
|
|
channelId,
|
|
name,
|
|
icon,
|
|
ownerId,
|
|
nicks,
|
|
nsfw,
|
|
requestCache,
|
|
}: {
|
|
userId: UserID;
|
|
channelId: ChannelID;
|
|
name?: string | null;
|
|
icon?: string | null;
|
|
ownerId?: UserID;
|
|
nicks?: Record<string, string | null> | null;
|
|
nsfw?: boolean;
|
|
requestCache: RequestCache;
|
|
}): Promise<Channel> {
|
|
const channel = await this.channelRepository.channelData.findUnique(channelId);
|
|
if (!channel) throw new UnknownChannelError();
|
|
if (channel.type !== ChannelTypes.GROUP_DM) {
|
|
throw new InvalidChannelTypeError();
|
|
}
|
|
if (!channel.recipientIds.has(userId)) {
|
|
throw new MissingAccessError();
|
|
}
|
|
const dmModCtx = {
|
|
userId,
|
|
guildId: null,
|
|
channelId,
|
|
messageId: null,
|
|
surface: 'profile_field' as const,
|
|
};
|
|
contentModerationService.scanText(name ?? null, dmModCtx);
|
|
if (nicks && typeof nicks === 'object') {
|
|
for (const nickValue of Object.values(nicks)) {
|
|
contentModerationService.scanText(nickValue, dmModCtx);
|
|
}
|
|
}
|
|
const updates: Partial<ChannelRow> = {};
|
|
if (ownerId !== undefined) {
|
|
if (channel.ownerId !== userId) {
|
|
throw new MissingPermissionsError();
|
|
}
|
|
if (!channel.recipientIds.has(ownerId)) {
|
|
throw new UnknownUserError();
|
|
}
|
|
const newOwnerUser = await this.userRepository.findUnique(ownerId);
|
|
if (newOwnerUser?.isBot) {
|
|
throw new CannotTransferOwnershipToBotError();
|
|
}
|
|
updates.owner_id = ownerId;
|
|
}
|
|
if (name !== undefined) {
|
|
updates.name = name;
|
|
}
|
|
if (nicks !== undefined) {
|
|
if (nicks === null) {
|
|
if (channel.ownerId !== userId) {
|
|
throw new MissingPermissionsError();
|
|
}
|
|
updates.nicks = null;
|
|
} else {
|
|
const isOwner = channel.ownerId === userId;
|
|
for (const targetUserId of Object.keys(nicks)) {
|
|
const targetUserIdBigInt = BigInt(targetUserId) as UserID;
|
|
if (!channel.recipientIds.has(targetUserIdBigInt) && targetUserIdBigInt !== userId) {
|
|
throw new UnknownUserError();
|
|
}
|
|
if (!isOwner && targetUserId !== userId.toString()) {
|
|
throw new MissingPermissionsError();
|
|
}
|
|
}
|
|
const updatedNicknames = new Map(channel.nicknames);
|
|
for (const [targetUserId, nickname] of Object.entries(nicks)) {
|
|
if (nickname === null || nickname.trim() === '') {
|
|
updatedNicknames.delete(targetUserId);
|
|
} else {
|
|
updatedNicknames.set(targetUserId, nickname.trim());
|
|
}
|
|
}
|
|
updates.nicks = updatedNicknames.size > 0 ? (updatedNicknames as Map<string, string>) : null;
|
|
}
|
|
}
|
|
if (nsfw !== undefined && nsfw !== channel.isNsfw) {
|
|
if (channel.ownerId !== userId) {
|
|
throw new MissingPermissionsError();
|
|
}
|
|
if (nsfw) {
|
|
const recipients = await Promise.all(
|
|
Array.from(channel.recipientIds).map((id) => this.userRepository.findUnique(id)),
|
|
);
|
|
if (recipients.some((recipient) => !recipient || !canUserAccessNsfwContent(recipient))) {
|
|
throw new GroupDmMatureContentIneligibleError();
|
|
}
|
|
}
|
|
updates.nsfw = nsfw;
|
|
}
|
|
let iconHash: string | null = null;
|
|
if (icon !== undefined) {
|
|
iconHash = await this.avatarService.uploadAvatar({
|
|
prefix: 'icons',
|
|
entityId: channelId,
|
|
errorPath: 'icon',
|
|
previousKey: channel.iconHash,
|
|
base64Image: icon,
|
|
});
|
|
updates.icon_hash = iconHash;
|
|
}
|
|
const updatedChannel = await this.channelRepository.channelData.upsert({
|
|
...channel.toRow(),
|
|
...updates,
|
|
});
|
|
if (name !== undefined && name !== channel.name) {
|
|
const messageId = createMessageID(await this.snowflakeService.generateForChannel(channelId));
|
|
const message = await this.messagePersistenceService.createSystemMessage({
|
|
messageId,
|
|
channelId,
|
|
userId,
|
|
type: MessageTypes.CHANNEL_NAME_CHANGE,
|
|
content: name,
|
|
});
|
|
await this.channelUtilsService.dispatchMessageCreate({
|
|
channel: updatedChannel,
|
|
message,
|
|
requestCache,
|
|
});
|
|
}
|
|
if (icon !== undefined && iconHash !== channel.iconHash) {
|
|
const messageId = createMessageID(await this.snowflakeService.generateForChannel(channelId));
|
|
const message = await this.messagePersistenceService.createSystemMessage({
|
|
messageId,
|
|
channelId,
|
|
userId,
|
|
type: MessageTypes.CHANNEL_ICON_CHANGE,
|
|
content: iconHash,
|
|
});
|
|
await this.channelUtilsService.dispatchMessageCreate({
|
|
channel: updatedChannel,
|
|
message,
|
|
requestCache,
|
|
});
|
|
}
|
|
await this.channelUtilsService.dispatchChannelUpdate({channel: updatedChannel, requestCache});
|
|
return updatedChannel;
|
|
}
|
|
}
|