Add stats, feeds, scheduler, and guild backup features to the bot

- Introduced new models in the Prisma schema for statistics, social feeds, scheduled messages, and guild backups, enhancing the bot's functionality.
- Implemented command modules for managing stats channels, feeds, scheduling messages, and creating/restoring backups, improving user engagement and server management.
- Updated job handling to include dedicated workers for stats updates, feed polling, scheduling, and backup restoration, enhancing automation.
- Enhanced command localization for new features in both German and English.
- 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:36:39 +02:00
parent 52b10c9536
commit 12066befa8
36 changed files with 4032 additions and 42 deletions

View File

@@ -0,0 +1,116 @@
CREATE TABLE "StatsConfig" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"membersChannelId" TEXT,
"onlineChannelId" TEXT,
"boostsChannelId" TEXT,
"membersTemplate" TEXT NOT NULL DEFAULT 'Members: {count}',
"onlineTemplate" TEXT NOT NULL DEFAULT 'Online: {count}',
"boostsTemplate" TEXT NOT NULL DEFAULT 'Boosts: {count}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StatsConfig_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "InviteStat" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"inviterId" TEXT NOT NULL,
"code" TEXT,
"joins" INTEGER NOT NULL DEFAULT 0,
"leaves" INTEGER NOT NULL DEFAULT 0,
"fakes" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "InviteStat_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "InviteJoin" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"inviterId" TEXT,
"code" TEXT,
"isFake" BOOLEAN NOT NULL DEFAULT false,
"leftAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "InviteJoin_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "ActivityStat" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"channelId" TEXT,
"messageCount" INTEGER NOT NULL DEFAULT 0,
"voiceMinutes" INTEGER NOT NULL DEFAULT 0,
"day" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ActivityStat_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "SocialFeed" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"sourceId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"rolePingId" TEXT,
"template" TEXT,
"lastItemId" TEXT,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "SocialFeed_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "ScheduledMessage" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"channelId" TEXT NOT NULL,
"creatorId" TEXT NOT NULL,
"content" TEXT,
"embed" JSONB,
"rolePingId" TEXT,
"cron" TEXT,
"runAt" TIMESTAMP(3),
"jobId" TEXT,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ScheduledMessage_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "GuildBackup" (
"id" TEXT NOT NULL,
"guildId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdById" TEXT NOT NULL,
"payload" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "GuildBackup_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "StatsConfig_guildId_key" ON "StatsConfig"("guildId");
CREATE UNIQUE INDEX "InviteStat_guildId_inviterId_key" ON "InviteStat"("guildId", "inviterId");
CREATE INDEX "InviteStat_guildId_joins_idx" ON "InviteStat"("guildId", "joins");
CREATE UNIQUE INDEX "InviteJoin_guildId_userId_key" ON "InviteJoin"("guildId", "userId");
CREATE INDEX "InviteJoin_guildId_inviterId_idx" ON "InviteJoin"("guildId", "inviterId");
CREATE UNIQUE INDEX "ActivityStat_guildId_userId_channelId_day_key" ON "ActivityStat"("guildId", "userId", "channelId", "day");
CREATE INDEX "ActivityStat_guildId_day_idx" ON "ActivityStat"("guildId", "day");
CREATE INDEX "SocialFeed_guildId_type_idx" ON "SocialFeed"("guildId", "type");
CREATE INDEX "SocialFeed_enabled_idx" ON "SocialFeed"("enabled");
CREATE INDEX "ScheduledMessage_guildId_enabled_idx" ON "ScheduledMessage"("guildId", "enabled");
CREATE INDEX "GuildBackup_guildId_createdAt_idx" ON "GuildBackup"("guildId", "createdAt");
ALTER TABLE "StatsConfig" ADD CONSTRAINT "StatsConfig_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "InviteStat" ADD CONSTRAINT "InviteStat_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "InviteJoin" ADD CONSTRAINT "InviteJoin_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ActivityStat" ADD CONSTRAINT "ActivityStat_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "SocialFeed" ADD CONSTRAINT "SocialFeed_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "ScheduledMessage" ADD CONSTRAINT "ScheduledMessage_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "GuildBackup" ADD CONSTRAINT "GuildBackup_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -44,6 +44,13 @@ model Guild {
birthdayConfig BirthdayConfig?
tempVoiceConfig TempVoiceConfig?
tempVoiceChannels TempVoiceChannel[]
statsConfig StatsConfig?
inviteStats InviteStat[]
inviteJoins InviteJoin[]
activityStats ActivityStat[]
socialFeeds SocialFeed[]
scheduledMessages ScheduledMessage[]
guildBackups GuildBackup[]
}
model GuildSettings {
@@ -639,3 +646,116 @@ model TempVoiceChannel {
@@index([guildId, ownerId])
}
model StatsConfig {
id String @id @default(cuid())
guildId String @unique
enabled Boolean @default(false)
membersChannelId String?
onlineChannelId String?
boostsChannelId String?
membersTemplate String @default("Members: {count}")
onlineTemplate String @default("Online: {count}")
boostsTemplate String @default("Boosts: {count}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
}
model InviteStat {
id String @id @default(cuid())
guildId String
inviterId String
code String?
joins Int @default(0)
leaves Int @default(0)
fakes Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, inviterId])
@@index([guildId, joins])
}
model InviteJoin {
id String @id @default(cuid())
guildId String
userId String
inviterId String?
code String?
isFake Boolean @default(false)
leftAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId])
@@index([guildId, inviterId])
}
model ActivityStat {
id String @id @default(cuid())
guildId String
userId String
channelId String?
messageCount Int @default(0)
voiceMinutes Int @default(0)
day DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@unique([guildId, userId, channelId, day])
@@index([guildId, day])
}
model SocialFeed {
id String @id @default(cuid())
guildId String
type String
sourceId String
channelId String
rolePingId String?
template String?
lastItemId String?
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, type])
@@index([enabled])
}
model ScheduledMessage {
id String @id @default(cuid())
guildId String
channelId String
creatorId String
content String?
embed Json?
rolePingId String?
cron String?
runAt DateTime?
jobId String?
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, enabled])
}
model GuildBackup {
id String @id @default(cuid())
guildId String
name String
createdById String
payload Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
@@index([guildId, createdAt])
}