feat(api): drop NSFW classification for emojis and stickers (#2462)

This commit is contained in:
Hampus
2026-09-04 22:55:58 +02:00
committed by GitHub
parent 1e6c332eae
commit 51cb750502
191 changed files with 18464 additions and 19351 deletions
@@ -6,7 +6,6 @@ import {ATTACHMENT_MAX_SIZE_NON_PREMIUM} from '@fluxer/constants/src/LimitConsta
import {UserFlags} from '@fluxer/constants/src/UserConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {NsfwEmojiStickerBlockedError} from '@fluxer/errors/src/domains/moderation/NsfwEmojiStickerBlockedError';
import type {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
import type {RichEmbedRequest} from '@fluxer/schema/src/domains/message/MessageRequestSchemas';
@@ -15,8 +14,8 @@ import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
import * as BucketUtils from '@fluxer/snowflake/src/SnowflakeBuckets';
import type {IVirusScanService} from '@pkgs/virus_scan/src/IVirusScanService';
import {AttachmentDecayService} from '../../../attachment/AttachmentDecayService';
import type {ChannelID, EmojiID, GuildID, MessageID, RoleID, StickerID, UserID, WebhookID} from '../../../BrandedTypes';
import {createAttachmentID, createEmojiID, createGuildID} from '../../../BrandedTypes';
import type {ChannelID, GuildID, MessageID, RoleID, StickerID, UserID, WebhookID} from '../../../BrandedTypes';
import {createAttachmentID, createGuildID} from '../../../BrandedTypes';
import {Config} from '../../../Config';
import {getContentMessage} from '../../../content_i18n/ContentI18n';
import type {
@@ -121,7 +120,7 @@ export class MessagePersistenceService {
constructor(
private channelRepository: IChannelRepositoryAggregate,
private userRepository: IUserRepository,
private guildRepository: IGuildRepositoryAggregate,
guildRepository: IGuildRepositoryAggregate,
private embedService: EmbedService,
private readonly storageService: IStorageService,
attachmentUploadTraceRepository: AttachmentUploadTraceRepository,
@@ -168,18 +167,10 @@ export class MessagePersistenceService {
isBot,
dmNsfwContext: params.dmNsfwContext,
});
let nsfwEmojiIds = new Set<EmojiID>();
if (params.content) {
if (!isNSFWAllowed) {
await this.enforceNsfwEmojiRestrictions(params.content);
} else {
nsfwEmojiIds = await this.collectNsfwEmojiIds(params.content);
}
}
const [sanitizedContent, attachmentResult, processedStickers] = await Promise.all([
this.sanitizeContentIfNeeded(params, authorId),
this.processAttachments(params, isNSFWAllowed ? 'allow' : 'block'),
this.processStickers(params, authorId, isNSFWAllowed),
this.processStickers(params, authorId),
]);
let messageContent = sanitizedContent;
let processedAttachments: Array<MessageAttachment> = params.processedAttachments
@@ -242,7 +233,6 @@ export class MessagePersistenceService {
? params.messageSnapshots.map((snapshot) => snapshot.toMessageSnapshot())
: null,
call: null,
nsfw_emojis: nsfwEmojiIds.size > 0 ? nsfwEmojiIds : null,
has_reaction: false,
version: 1,
};
@@ -305,7 +295,6 @@ export class MessagePersistenceService {
private async processStickers(
params: CreateMessageParams,
authorId: UserID | null,
isNSFWAllowed?: boolean,
): Promise<Array<MessageStickerItem>> {
if (!params.stickerIds || params.stickerIds.length === 0) {
return [];
@@ -315,37 +304,9 @@ export class MessagePersistenceService {
userId: authorId,
guildId: params.guildId,
hasPermission: params.hasPermission,
isNSFWAllowed: isNSFWAllowed ?? true,
});
}
private async enforceNsfwEmojiRestrictions(content: string): Promise<void> {
const nsfwIds = await this.collectNsfwEmojiIds(content);
if (nsfwIds.size > 0) {
throw new NsfwEmojiStickerBlockedError();
}
}
private async collectNsfwEmojiIds(content: string): Promise<Set<EmojiID>> {
const CUSTOM_EMOJI_REGEX = /<a?:[^:]+:(\d+)>/g;
const emojiIds = new Set<EmojiID>();
let match: RegExpExecArray | null;
while ((match = CUSTOM_EMOJI_REGEX.exec(content)) !== null) {
emojiIds.add(createEmojiID(BigInt(match[1])));
}
if (emojiIds.size === 0) {
return new Set();
}
const lookups = await Promise.all([...emojiIds].map((id) => this.guildRepository.getEmojiById(id)));
const nsfwEmojiIds = new Set<EmojiID>();
for (const emoji of lookups) {
if (emoji?.isNsfw) {
nsfwEmojiIds.add(emoji.id);
}
}
return nsfwEmojiIds;
}
private async runPostPersistenceOperations(context: {
message: Message;
params: CreateMessageParams;
@@ -431,9 +392,6 @@ export class MessagePersistenceService {
if (data.content !== undefined && data.content !== message.content) {
let sanitizedContent = data.content && hasVisibleContent(data.content) ? data.content : '';
if (sanitizedContent) {
if (!isNSFWAllowed) {
await this.enforceNsfwEmojiRestrictions(sanitizedContent);
}
sanitizedContent = await this.contentService.sanitizeCustomEmojis({
content: sanitizedContent,
userId: message.authorId ?? null,
@@ -96,7 +96,6 @@ function makeMessage(messageId: bigint = 2n, content: string = '', channelId: bi
message_reference: null,
message_snapshots: null,
call: null,
nsfw_emojis: null,
has_reaction: null,
version: 1,
});
@@ -48,7 +48,6 @@ function makeMessage(channelId: ChannelID, messageId: MessageID): Message {
message_reference: null,
message_snapshots: null,
call: null,
nsfw_emojis: null,
has_reaction: null,
version: 1,
});
@@ -18,7 +18,7 @@ import {FeatureTemporarilyDisabledError} from '@fluxer/errors/src/domains/core/F
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {MissingPermissionsError} from '@fluxer/errors/src/domains/core/MissingPermissionsError';
import {SlowmodeRateLimitError} from '@fluxer/errors/src/domains/core/SlowmodeRateLimitError';
import {NsfwEmojiStickerBlockedError} from '@fluxer/errors/src/domains/moderation/NsfwEmojiStickerBlockedError';
import {NsfwContentRequiresAgeVerificationError} from '@fluxer/errors/src/domains/moderation/NsfwContentRequiresAgeVerificationError';
import type {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import type {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
import {snowflakeToDate} from '@fluxer/snowflake/src/Snowflake';
@@ -845,7 +845,7 @@ export class MessageSendService {
const guildNsfw = guild != null && guild.nsfw_level === GuildNSFWLevel.AGE_RESTRICTED;
const destAllowsNsfw = channel.isNsfw || guildNsfw;
if (!destAllowsNsfw) {
throw new NsfwEmojiStickerBlockedError();
throw new NsfwContentRequiresAgeVerificationError();
}
}
if (data.message_reference && guild && !isForwardMessage) {
@@ -4,7 +4,6 @@ import {Permissions} from '@fluxer/constants/src/ChannelConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import {MissingPermissionsError} from '@fluxer/errors/src/domains/core/MissingPermissionsError';
import {NsfwEmojiStickerBlockedError} from '@fluxer/errors/src/domains/moderation/NsfwEmojiStickerBlockedError';
import type {GuildID, StickerID, UserID} from '../../../BrandedTypes';
import type {MessageStickerItem} from '../../../database/types/MessageTypes';
import type {IGuildRepositoryAggregate} from '../../../guild/repositories/IGuildRepositoryAggregate';
@@ -25,9 +24,8 @@ export class MessageStickerService {
userId: UserID | null;
guildId: GuildID | null;
hasPermission?: (permission: bigint) => Promise<boolean>;
isNSFWAllowed?: boolean;
}): Promise<Array<MessageStickerItem>> {
const {stickerIds, userId, guildId, hasPermission, isNSFWAllowed = true} = params;
const {stickerIds, userId, guildId, hasPermission} = params;
let hasGlobalExpressions = 0;
if (userId) {
const user = await this.userRepository.findUnique(userId);
@@ -49,26 +47,18 @@ export class MessageStickerService {
if (!stickerFromAnyGuild) {
throw InputValidationError.fromCode('sticker', ValidationErrorCodes.CUSTOM_STICKER_NOT_FOUND);
}
if (!isNSFWAllowed && stickerFromAnyGuild.isNsfw) {
throw new NsfwEmojiStickerBlockedError();
}
return {
sticker_id: stickerFromAnyGuild.id,
name: stickerFromAnyGuild.name,
animated: stickerFromAnyGuild.animated,
...(stickerFromAnyGuild.isNsfw ? {nsfw: true} : {}),
};
}
const guildSticker = await this.guildRepository.getSticker(stickerId, guildId);
if (guildSticker) {
if (!isNSFWAllowed && guildSticker.isNsfw) {
throw new NsfwEmojiStickerBlockedError();
}
return {
sticker_id: guildSticker.id,
name: guildSticker.name,
animated: guildSticker.animated,
...(guildSticker.isNsfw ? {nsfw: true} : {}),
};
}
const stickerFromOtherGuild = await this.guildRepository.getStickerById(stickerId);
@@ -87,14 +77,10 @@ export class MessageStickerService {
throw new MissingPermissionsError();
}
}
if (!isNSFWAllowed && stickerFromOtherGuild.isNsfw) {
throw new NsfwEmojiStickerBlockedError();
}
return {
sticker_id: stickerFromOtherGuild.id,
name: stickerFromOtherGuild.name,
animated: stickerFromOtherGuild.animated,
...(stickerFromOtherGuild.isNsfw ? {nsfw: true} : {}),
};
}),
);