Add giveaway, ticket, self-role, starboard, and suggestion features to the bot

- Introduced new models in the Prisma schema for giveaways, tickets, self-role panels, starboard configurations, and suggestions, enhancing the bot's functionality.
- Implemented job handling for self-role expiration and ticket management, improving user experience.
- Updated command localization to support new features in both German and English.
- Enhanced the job processing system to include dedicated workers for managing tickets and self-role expirations.
- Documented the new features and their setup processes in PHASE-TRACKING.md for clarity on implementation steps.
This commit is contained in:
smueller
2026-07-22 13:08:37 +02:00
parent f2f856628d
commit a583db7a15
50 changed files with 7820 additions and 4 deletions

View File

@@ -0,0 +1,246 @@
CREATE TABLE "Giveaway" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"messageId" TEXT,
"prize" TEXT NOT NULL,
"winnerCount" INTEGER NOT NULL DEFAULT 1,
"endsAt" TIMESTAMP(3) NOT NULL,
"requiredRoleId" TEXT,
"requiredLevel" INTEGER,
"requiredMemberDays" INTEGER,
"paused" BOOLEAN NOT NULL DEFAULT false,
"endedAt" TIMESTAMP(3),
"winners" TEXT[] DEFAULT ARRAY[]::TEXT[],
"entrants" TEXT[] DEFAULT ARRAY[]::TEXT[],
"hostId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Giveaway_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "TicketConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"mode" TEXT NOT NULL DEFAULT 'CHANNEL',
"categoryId" TEXT,
"logChannelId" TEXT,
"transcriptDm" BOOLEAN NOT NULL DEFAULT false,
"inactivityHours" INTEGER NOT NULL DEFAULT 72,
"ratingEnabled" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TicketConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "TicketCategory" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"emoji" TEXT,
"supportRoleIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"formFields" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TicketCategory_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Ticket" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"categoryId" TEXT,
"openerId" TEXT NOT NULL,
"claimedById" TEXT,
"channelId" TEXT,
"threadId" TEXT,
"status" TEXT NOT NULL DEFAULT 'OPEN',
"priority" TEXT NOT NULL DEFAULT 'NORMAL',
"subject" TEXT,
"formAnswers" JSONB,
"transcriptHtml" TEXT,
"rating" INTEGER,
"closedAt" TIMESTAMP(3),
"lastActivityAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "SelfRolePanel" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"messageId" TEXT,
"title" TEXT NOT NULL,
"description" TEXT,
"mode" TEXT NOT NULL DEFAULT 'BUTTONS',
"behavior" TEXT NOT NULL DEFAULT 'NORMAL',
"roles" JSONB NOT NULL,
"expiresAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SelfRolePanel_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Tag" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"content" TEXT,
"embed" JSONB,
"responseType" TEXT NOT NULL DEFAULT 'TEXT',
"triggerWord" TEXT,
"allowedRoleIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"allowedChannelIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"createdById" TEXT NOT NULL,
"useCount" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "StarboardConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"channelId" TEXT,
"emoji" TEXT NOT NULL DEFAULT E'',
"threshold" INTEGER NOT NULL DEFAULT 3,
"allowSelfStar" BOOLEAN NOT NULL DEFAULT false,
"ignoredChannelIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StarboardConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "StarboardEntry" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"sourceChannelId" TEXT NOT NULL,
"sourceMessageId" TEXT NOT NULL,
"starboardMessageId" TEXT NOT NULL,
"starCount" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StarboardEntry_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "SuggestionConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"openChannelId" TEXT,
"approvedChannelId" TEXT,
"deniedChannelId" TEXT,
"createThread" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SuggestionConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Suggestion" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"authorId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'OPEN',
"messageId" TEXT,
"channelId" TEXT,
"threadId" TEXT,
"upvotes" INTEGER NOT NULL DEFAULT 0,
"downvotes" INTEGER NOT NULL DEFAULT 0,
"voterUpIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"voterDownIds" TEXT[] DEFAULT ARRAY[]::TEXT[],
"staffReason" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Suggestion_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "BirthdayConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"channelId" TEXT,
"roleId" TEXT,
"message" TEXT,
"timezone" TEXT NOT NULL DEFAULT 'Europe/Berlin',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BirthdayConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "Birthday" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"month" INTEGER NOT NULL,
"day" INTEGER NOT NULL,
"year" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Birthday_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "TempVoiceConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"hubChannelId" TEXT,
"categoryId" TEXT,
"defaultName" TEXT NOT NULL DEFAULT '{user}''s Channel',
"defaultLimit" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TempVoiceConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "TempVoiceChannel" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"ownerId" TEXT NOT NULL,
"controlMessageId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "TempVoiceChannel_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "Giveaway_guildId_endsAt_idx" ON "Giveaway"("guildId", "endsAt");
CREATE UNIQUE INDEX "TicketConfig_guildId_key" ON "TicketConfig"("guildId");
CREATE UNIQUE INDEX "TicketCategory_guildId_name_key" ON "TicketCategory"("guildId", "name");
CREATE INDEX "TicketCategory_guildId_idx" ON "TicketCategory"("guildId");
CREATE INDEX "Ticket_guildId_status_idx" ON "Ticket"("guildId", "status");
CREATE INDEX "Ticket_channelId_idx" ON "Ticket"("channelId");
CREATE INDEX "SelfRolePanel_guildId_idx" ON "SelfRolePanel"("guildId");
CREATE UNIQUE INDEX "Tag_guildId_name_key" ON "Tag"("guildId", "name");
CREATE INDEX "Tag_guildId_idx" ON "Tag"("guildId");
CREATE UNIQUE INDEX "StarboardConfig_guildId_key" ON "StarboardConfig"("guildId");
CREATE UNIQUE INDEX "StarboardEntry_guildId_sourceMessageId_key" ON "StarboardEntry"("guildId", "sourceMessageId");
CREATE INDEX "StarboardEntry_guildId_idx" ON "StarboardEntry"("guildId");
CREATE UNIQUE INDEX "SuggestionConfig_guildId_key" ON "SuggestionConfig"("guildId");
CREATE INDEX "Suggestion_guildId_status_idx" ON "Suggestion"("guildId", "status");
CREATE UNIQUE INDEX "BirthdayConfig_guildId_key" ON "BirthdayConfig"("guildId");
CREATE UNIQUE INDEX "Birthday_guildId_userId_key" ON "Birthday"("guildId", "userId");
CREATE INDEX "Birthday_guildId_month_day_idx" ON "Birthday"("guildId", "month", "day");
CREATE UNIQUE INDEX "TempVoiceConfig_guildId_key" ON "TempVoiceConfig"("guildId");
CREATE UNIQUE INDEX "TempVoiceChannel_channelId_key" ON "TempVoiceChannel"("channelId");
CREATE INDEX "TempVoiceChannel_guildId_ownerId_idx" ON "TempVoiceChannel"("guildId", "ownerId");
ALTER TABLE "Giveaway" ADD CONSTRAINT "Giveaway_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TicketConfig" ADD CONSTRAINT "TicketConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TicketCategory" ADD CONSTRAINT "TicketCategory_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Ticket" ADD CONSTRAINT "Ticket_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "TicketCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "SelfRolePanel" ADD CONSTRAINT "SelfRolePanel_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Tag" ADD CONSTRAINT "Tag_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "StarboardConfig" ADD CONSTRAINT "StarboardConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "StarboardEntry" ADD CONSTRAINT "StarboardEntry_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SuggestionConfig" ADD CONSTRAINT "SuggestionConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Suggestion" ADD CONSTRAINT "Suggestion_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "BirthdayConfig" ADD CONSTRAINT "BirthdayConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Birthday" ADD CONSTRAINT "Birthday_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TempVoiceConfig" ADD CONSTRAINT "TempVoiceConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "TempVoiceChannel" ADD CONSTRAINT "TempVoiceChannel_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -30,6 +30,20 @@ model Guild {
polls Poll[]
reminders Reminder[]
afkStatuses AfkStatus[]
giveaways Giveaway[]
ticketConfig TicketConfig?
ticketCategories TicketCategory[]
tickets Ticket[]
selfRolePanels SelfRolePanel[]
tags Tag[]
starboardConfig StarboardConfig?
starboardEntries StarboardEntry[]
suggestionConfig SuggestionConfig?
suggestions Suggestion[]
birthdays Birthday[]
birthdayConfig BirthdayConfig?
tempVoiceConfig TempVoiceConfig?
tempVoiceChannels TempVoiceChannel[]
}
model GuildSettings {
@@ -389,3 +403,239 @@ model AfkStatus {
@@unique([guildId, userId])
}
model Giveaway {
id String @id @default(cuid())
guildId String
channelId String
messageId String?
prize String
winnerCount Int @default(1)
endsAt DateTime
requiredRoleId String?
requiredLevel Int?
requiredMemberDays Int?
paused Boolean @default(false)
endedAt DateTime?
winners String[] @default([])
entrants String[] @default([])
hostId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, endsAt])
}
model TicketConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
mode String @default("CHANNEL")
categoryId String?
logChannelId String?
transcriptDm Boolean @default(false)
inactivityHours Int @default(72)
ratingEnabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model TicketCategory {
id String @id @default(cuid())
guildId String
name String
description String?
emoji String?
supportRoleIds String[] @default([])
formFields Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
tickets Ticket[]
@@unique([guildId, name])
@@index([guildId])
}
model Ticket {
id String @id @default(cuid())
guildId String
categoryId String?
openerId String
claimedById String?
channelId String?
threadId String?
status String @default("OPEN")
priority String @default("NORMAL")
subject String?
formAnswers Json?
transcriptHtml String?
rating Int?
closedAt DateTime?
lastActivityAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
category TicketCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
@@index([guildId, status])
@@index([channelId])
}
model SelfRolePanel {
id String @id @default(cuid())
guildId String
channelId String
messageId String?
title String
description String?
mode String @default("BUTTONS")
behavior String @default("NORMAL")
roles Json
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId])
}
model Tag {
id String @id @default(cuid())
guildId String
name String
content String?
embed Json?
responseType String @default("TEXT")
triggerWord String?
allowedRoleIds String[] @default([])
allowedChannelIds String[] @default([])
createdById String
useCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, name])
@@index([guildId])
}
model StarboardConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
channelId String?
emoji String @default("⭐")
threshold Int @default(3)
allowSelfStar Boolean @default(false)
ignoredChannelIds String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model StarboardEntry {
id String @id @default(cuid())
guildId String
sourceChannelId String
sourceMessageId String
starboardMessageId String
starCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, sourceMessageId])
@@index([guildId])
}
model SuggestionConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
openChannelId String?
approvedChannelId String?
deniedChannelId String?
createThread Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model Suggestion {
id String @id @default(cuid())
guildId String
authorId String
content String
status String @default("OPEN")
messageId String?
channelId String?
threadId String?
upvotes Int @default(0)
downvotes Int @default(0)
voterUpIds String[] @default([])
voterDownIds String[] @default([])
staffReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, status])
}
model BirthdayConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(true)
channelId String?
roleId String?
message String?
timezone String @default("Europe/Berlin")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model Birthday {
id String @id @default(cuid())
guildId String
userId String
month Int
day Int
year Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, month, day])
}
model TempVoiceConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
hubChannelId String?
categoryId String?
defaultName String @default("{user}'s Channel")
defaultLimit Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model TempVoiceChannel {
id String @id @default(cuid())
guildId String
channelId String @unique
ownerId String
controlMessageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, ownerId])
}