feat(guild): require opt-in for emoji and sticker cloning (#2712)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-12 00:33:23 +02:00
1 parent 7b15e5be0f
commit 3affd295e8
63 files changed
+4189 -1752

No files matched your search

@@ -35,8 +35,8 @@ export const CloneAllowedToggle = observer(({guildId, kind}: CloneAllowedToggleP
if (saving || !canManageGuild) return;
setSaving(true);
try {
const feature = kind === 'emoji' ? GuildFeatures.CLONE_EMOJI_DISABLED : GuildFeatures.CLONE_STICKER_DISABLED;
await GuildCommands.toggleFeature(guildId, feature, !nextAllowed);
const feature = kind === 'emoji' ? GuildFeatures.CLONE_EMOJI_ENABLED : GuildFeatures.CLONE_STICKER_ENABLED;
await GuildCommands.toggleFeature(guildId, feature, nextAllowed);
} catch (error) {
logger.error(`Failed to toggle clone-${kind}-allowed for guild ${guildId}`, error);
ModalCommands.push(
@@ -52,21 +52,19 @@ export const CloneAllowedToggle = observer(({guildId, kind}: CloneAllowedToggleP
}
};
const label =
kind === 'emoji' ? (
<Trans>Allow others to clone your emojis</Trans>
) : (
<Trans>Allow others to clone your stickers</Trans>
);
kind === 'emoji' ? <Trans>Let others clone your emojis</Trans> : <Trans>Let others clone your stickers</Trans>;
const description =
kind === 'emoji' ? (
<Trans>
When enabled, members of other communities can use the in-app one-click "Clone" shortcut on your custom emojis.
This does not prevent them from saving the image and uploading it themselves.
This is off until you turn it on. While it is on, members of other communities can use the in-app one-click
"Clone" shortcut on your custom emojis. Turning it off does not prevent anyone from saving the image and
uploading it themselves.
</Trans>
) : (
<Trans>
When enabled, members of other communities can use the in-app one-click "Clone" shortcut on your custom
stickers. This does not prevent them from saving the image and uploading it themselves.
This is off until you turn it on. While it is on, members of other communities can use the in-app one-click
"Clone" shortcut on your custom stickers. Turning it off does not prevent anyone from saving the image and
uploading it themselves.
</Trans>
);
return (
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {Guild} from '@app/features/guild/models/Guild';
import {GuildFeatures} from '@fluxer/constants/src/GuildConstants';
import type {Guild as WireGuild} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
import {describe, expect, test, vi} from 'vitest';
vi.mock('@app/features/app/state/RuntimeConfig', () => ({default: {localInstanceDomain: 'fluxer.test'}}));
const createGuild = (features: Array<string>): Guild =>
new Guild({
id: '1',
name: 'Test Community',
icon: null,
features,
roles: [],
owner_id: '2',
} as unknown as WireGuild);
describe('Guild clone permissions', () => {
test('no features means cloning is not allowed', () => {
const guild = createGuild([]);
expect(guild.cloneEmojiAllowed).toBe(false);
expect(guild.cloneStickerAllowed).toBe(false);
});
test('the enabled features allow cloning', () => {
const guild = createGuild([GuildFeatures.CLONE_EMOJI_ENABLED, GuildFeatures.CLONE_STICKER_ENABLED]);
expect(guild.cloneEmojiAllowed).toBe(true);
expect(guild.cloneStickerAllowed).toBe(true);
});
test('each enabled feature only allows its own kind', () => {
const emojiOnly = createGuild([GuildFeatures.CLONE_EMOJI_ENABLED]);
expect(emojiOnly.cloneEmojiAllowed).toBe(true);
expect(emojiOnly.cloneStickerAllowed).toBe(false);
const stickerOnly = createGuild([GuildFeatures.CLONE_STICKER_ENABLED]);
expect(stickerOnly.cloneEmojiAllowed).toBe(false);
expect(stickerOnly.cloneStickerAllowed).toBe(true);
});
test('the deprecated disabled features do not allow cloning', () => {
const guild = createGuild([GuildFeatures.CLONE_EMOJI_DISABLED, GuildFeatures.CLONE_STICKER_DISABLED]);
expect(guild.cloneEmojiAllowed).toBe(false);
expect(guild.cloneStickerAllowed).toBe(false);
});
test('the deprecated disabled features do not override the enabled features', () => {
const guild = createGuild([
GuildFeatures.CLONE_EMOJI_DISABLED,
GuildFeatures.CLONE_STICKER_DISABLED,
GuildFeatures.CLONE_EMOJI_ENABLED,
GuildFeatures.CLONE_STICKER_ENABLED,
]);
expect(guild.cloneEmojiAllowed).toBe(true);
expect(guild.cloneStickerAllowed).toBe(true);
});
});
@@ -451,11 +451,11 @@ export class Guild {
}
get cloneEmojiAllowed(): boolean {
return !this.features.has(GuildFeatures.CLONE_EMOJI_DISABLED);
return this.features.has(GuildFeatures.CLONE_EMOJI_ENABLED);
}
get cloneStickerAllowed(): boolean {
return !this.features.has(GuildFeatures.CLONE_STICKER_DISABLED);
return this.features.has(GuildFeatures.CLONE_STICKER_ENABLED);
}
get isLargeGuild(): boolean {
@@ -95,6 +95,14 @@ const STICKER_CLONING_DISABLED_DESCRIPTOR = msg({
message: 'Sticker cloning disabled',
comment: 'Audit log community feature label indicating sticker cloning is disabled.',
});
const EMOJI_CLONING_ENABLED_DESCRIPTOR = msg({
message: 'Emoji cloning enabled',
comment: 'Audit log community feature label indicating emoji cloning is enabled.',
});
const STICKER_CLONING_ENABLED_DESCRIPTOR = msg({
message: 'Sticker cloning enabled',
comment: 'Audit log community feature label indicating sticker cloning is enabled.',
});
const DETACHED_BANNER_DESCRIPTOR = msg({
message: 'Detached banner',
comment: 'Audit log community feature label for detached banner layout support.',
@@ -425,6 +433,8 @@ const featureLabelMap: Record<string, MessageDescriptor> = {
[GuildFeatures.BANNER]: BANNER_DESCRIPTOR,
[GuildFeatures.CLONE_EMOJI_DISABLED]: EMOJI_CLONING_DISABLED_DESCRIPTOR,
[GuildFeatures.CLONE_STICKER_DISABLED]: STICKER_CLONING_DISABLED_DESCRIPTOR,
[GuildFeatures.CLONE_EMOJI_ENABLED]: EMOJI_CLONING_ENABLED_DESCRIPTOR,
[GuildFeatures.CLONE_STICKER_ENABLED]: STICKER_CLONING_ENABLED_DESCRIPTOR,
[GuildFeatures.DETACHED_BANNER]: DETACHED_BANNER_DESCRIPTOR,
[GuildFeatures.INVITE_SPLASH]: INVITE_SPLASH_DESCRIPTOR,
[GuildFeatures.INVITES_DISABLED]: INVITES_DISABLED_DESCRIPTOR,