Enhance tag management with cooldown feature and related updates

- Added a `cooldownSeconds` field to the `Tag` model, allowing for customizable cooldown periods between tag responses.
- Updated tag command handling to incorporate cooldown logic, preventing spam and improving user experience.
- Enhanced the WebUI to support cooldown configuration for tags, including input validation and localization for cooldown messages.
- Refactored tag-related service functions to manage cooldowns effectively, ensuring accurate tracking and enforcement.
- Improved localization for new cooldown-related messages in both English and German.
This commit is contained in:
TheOnlyMace
2026-07-25 16:50:46 +02:00
parent 5f932f5d63
commit bc4fae5415
21 changed files with 318 additions and 39 deletions

View File

@@ -157,6 +157,19 @@ describe('dashboard schemas', () => {
allowedChannelIds: []
});
expect(parsed.name).toBe('rules');
expect(parsed.cooldownSeconds).toBe(15);
});
it('accepts custom tag cooldown', () => {
const parsed = TagDashboardCreateSchema.parse({
name: 'rules',
content: 'Please read the rules',
responseType: 'TEXT',
cooldownSeconds: 30,
allowedRoleIds: [],
allowedChannelIds: []
});
expect(parsed.cooldownSeconds).toBe(30);
});
it('validates starboard patches', () => {

View File

@@ -516,6 +516,8 @@ export const TagDashboardSchema = z.object({
components: MessageComponentsV2Schema.nullable().optional(),
responseType: TagResponseTypeSchema,
triggerWord: z.string().max(100).nullable().optional(),
/** Seconds between auto-responder /tag replies; 0 disables. Default 15. */
cooldownSeconds: z.number().int().min(0).max(3600).default(15),
allowedRoleIds: z.array(z.string()),
allowedChannelIds: z.array(z.string())
});

View File

@@ -566,12 +566,15 @@ const de: Dictionary = {
'tag.info.uses': 'Aufrufe: {count}',
'tag.info.trigger': 'Trigger: `{word}`',
'tag.info.noTrigger': 'Kein Auto-Responder.',
'tag.info.cooldown': 'Cooldown: {seconds}s',
'tag.info.noCooldown': 'Cooldown: aus',
'tag.info.roles': 'Rollen: {roles}',
'tag.info.allRoles': 'Rollen: alle',
'tag.info.channels': 'Kanäle: {channels}',
'tag.info.allChannels': 'Kanäle: alle',
'tag.error.not_found': 'Tag nicht gefunden.',
'tag.error.not_allowed': 'Du darfst diesen Tag hier nicht nutzen.',
'tag.error.cooldown': 'Cooldown aktiv. Versuche es in {seconds} Sekunden erneut.',
'tag.error.invalid_name': 'Ungültiger Tag-Name (132 Zeichen, a-z, 0-9, _, -).',
'tag.error.content_required': 'Text-Tags benötigen Inhalt.',
'tag.error.embed_required': 'Embed-Tags benötigen Titel oder Beschreibung.',
@@ -1179,12 +1182,15 @@ const en: Dictionary = {
'tag.info.uses': 'Uses: {count}',
'tag.info.trigger': 'Trigger: `{word}`',
'tag.info.noTrigger': 'No auto-responder.',
'tag.info.cooldown': 'Cooldown: {seconds}s',
'tag.info.noCooldown': 'Cooldown: off',
'tag.info.roles': 'Roles: {roles}',
'tag.info.allRoles': 'Roles: all',
'tag.info.channels': 'Channels: {channels}',
'tag.info.allChannels': 'Channels: all',
'tag.error.not_found': 'Tag not found.',
'tag.error.not_allowed': 'You are not allowed to use this tag here.',
'tag.error.cooldown': 'Cooldown active. Try again in {seconds} seconds.',
'tag.error.invalid_name': 'Invalid tag name (132 chars, a-z, 0-9, _, -).',
'tag.error.content_required': 'Text tags require content.',
'tag.error.embed_required': 'Embed tags require a title or description.',