feat: add quote feature

Now users can post quotes about each other if guild has enabled it!
This commit is contained in:
Axel Olausson Holtenäs 2023-05-29 16:08:22 +02:00
parent 21a5cd3eab
commit a2e1fa7a98
7 changed files with 300 additions and 4 deletions

View file

@ -0,0 +1,40 @@
-- AlterTable
ALTER TABLE `GuildSettings` ADD COLUMN `guildQuotesSettingsId` VARCHAR(191) NULL;
-- CreateTable
CREATE TABLE `GuildQuotesSettings` (
`id` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
`quoteChannelId` VARCHAR(191) NOT NULL,
UNIQUE INDEX `GuildQuotesSettings_id_key`(`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Quotes` (
`id` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
`userId` VARCHAR(191) NOT NULL,
`guildId` VARCHAR(191) NOT NULL,
`message` VARCHAR(191) NOT NULL,
`posterUserId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `GuildSettings` ADD CONSTRAINT `GuildSettings_guildQuotesSettingsId_fkey` FOREIGN KEY (`guildQuotesSettingsId`) REFERENCES `GuildQuotesSettings`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `GuildQuotesSettings` ADD CONSTRAINT `GuildQuotesSettings_id_fkey` FOREIGN KEY (`id`) REFERENCES `Guild`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Quotes` ADD CONSTRAINT `Quotes_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Quotes` ADD CONSTRAINT `Quotes_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `Guild`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Quotes` ADD CONSTRAINT `Quotes_posterUserId_fkey` FOREIGN KEY (`posterUserId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `GuildQuotesSettings` ADD COLUMN `status` BOOLEAN NOT NULL DEFAULT false;

View file

@ -8,14 +8,17 @@ datasource db {
}
model Guild {
id String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guildMembers GuildMember[]
guildSettings GuildSettings?
guildCreditsSettings GuildCreditsSettings?
guildQuotesSettings GuildQuotesSettings?
apiCredentials ApiCredentials[]
cooldowns Cooldown[]
Quotes Quotes[]
}
model User {
@ -27,6 +30,8 @@ model User {
cooldowns Cooldown[]
userReputation UserReputation?
Quotes Quotes[] @relation(name: "Quotes")
PostedQuotes Quotes[] @relation(name: "PostedQuotes")
}
model GuildMember {
@ -81,6 +86,8 @@ model GuildSettings {
creditsSettings GuildCreditsSettings? @relation(fields: [guildCreditsSettingsId], references: [id], onDelete: Cascade)
guildCreditsSettingsId String?
GuildQuotesSettings GuildQuotesSettings? @relation(fields: [guildQuotesSettingsId], references: [id])
guildQuotesSettingsId String?
}
model GuildCreditsSettings {
@ -102,6 +109,36 @@ model GuildCreditsSettings {
guildSettings GuildSettings[]
}
model GuildQuotesSettings {
id String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
guild Guild @relation(fields: [id], references: [id], onDelete: Cascade)
status Boolean @default(false)
quoteChannelId String
guildSettings GuildSettings[]
}
model Quotes {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], name: "Quotes")
userId String
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
guildId String
message String
posterUserId String
posterUser User @relation(fields: [posterUserId], references: [id], name: "PostedQuotes")
}
model ApiCredentials {
id String @id @default(cuid())
createdAt DateTime @default(now())

View file

@ -0,0 +1,23 @@
import { ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
// Subcommands
import {
SubcommandHandlers,
executeSubcommand,
} from "../../handlers/executeSubcommand";
import * as post from "./subcommands/post";
const subcommandHandlers: SubcommandHandlers = {
post: post.execute,
};
export const builder = new SlashCommandBuilder()
.setName("quote")
.setDescription("Fun commands.")
.addSubcommand(post.builder);
// Execute function
export const execute = async (interaction: ChatInputCommandInteraction) => {
await executeSubcommand(interaction, subcommandHandlers);
};

View file

@ -0,0 +1,101 @@
import {
ChannelType,
ChatInputCommandInteraction,
EmbedBuilder,
SlashCommandSubcommandBuilder,
} from "discord.js";
import CooldownManager from "../../../../handlers/CooldownManager";
import prisma from "../../../../handlers/prisma";
import generateCooldownName from "../../../../helpers/generateCooldownName";
import deferReply from "../../../../utils/deferReply";
import sendResponse from "../../../../utils/sendResponse";
const cooldownManager = new CooldownManager();
export const builder = (command: SlashCommandSubcommandBuilder) => {
return command
.setName("post")
.setDescription("Post a quote someone said in this server")
.addUserOption((option) =>
option
.setName("user")
.setDescription("The user who said this")
.setRequired(true)
)
.addStringOption((option) =>
option
.setName("message")
.setDescription("What the user said")
.setRequired(true)
);
};
export const execute = async (
interaction: ChatInputCommandInteraction
): Promise<void> => {
await deferReply(interaction, true);
const { options, guild, user } = interaction;
const quoteUser = options.getUser("user", true);
const quoteString = options.getString("message", true);
if (!guild) throw new Error("A guild is required.");
const guildQuotesSettings = await prisma.guildQuotesSettings.findUnique({
where: { id: guild.id },
});
if (!guildQuotesSettings) throw new Error("No configuration available.");
if (guildQuotesSettings.status !== true)
throw new Error("Quotes are disabled in this server.");
const channel = await interaction.client.channels.fetch(
guildQuotesSettings.quoteChannelId
);
if (!channel) throw new Error("No channel found.");
if (channel.type !== ChannelType.GuildText)
throw new Error("The channel is not a text channel.");
await prisma.quotes.create({
data: {
guildId: guild.id,
userId: quoteUser.id,
posterUserId: user.id,
message: quoteString,
},
});
const quoteEmbed = new EmbedBuilder()
.setAuthor({
name: `Quote of ${quoteUser.username}`,
iconURL: quoteUser.displayAvatarURL(),
})
.setColor(process.env.EMBED_COLOR_SUCCESS)
.setDescription(quoteString)
.setFooter({
text: `Posted by ${user.username}`,
iconURL: user.displayAvatarURL(),
});
const sentMessage = await channel.send({ embeds: [quoteEmbed] });
await sentMessage.react("👍");
await sentMessage.react("👎");
const postEmbed = new EmbedBuilder()
.setColor(process.env.EMBED_COLOR_SUCCESS)
.setDescription("Successfully posted the quote!");
await sendResponse(interaction, { embeds: [postEmbed] });
await cooldownManager.setCooldown(
await generateCooldownName(interaction),
guild,
user,
5 * 60
);
};

View file

@ -6,10 +6,12 @@ import {
} from "../../handlers/executeSubcommand";
import * as credits from "./subcommands/credits";
import * as ctrlpanel from "./subcommands/ctrlpanel";
import * as quotes from "./subcommands/quotes";
const subcommandHandlers: SubcommandHandlers = {
ctrlpanel: ctrlpanel.execute,
credits: credits.execute,
quotes: quotes.execute,
};
export const builder = new SlashCommandBuilder()
@ -17,7 +19,8 @@ export const builder = new SlashCommandBuilder()
.setDescription("Manage guild configurations.")
.setDMPermission(false)
.addSubcommand(ctrlpanel.builder)
.addSubcommand(credits.builder);
.addSubcommand(credits.builder)
.addSubcommand(quotes.builder);
export const execute = async (interaction: ChatInputCommandInteraction) => {
await executeSubcommand(interaction, subcommandHandlers);

View file

@ -0,0 +1,90 @@
import {
ChatInputCommandInteraction,
EmbedBuilder,
PermissionsBitField,
SlashCommandSubcommandBuilder,
} from "discord.js";
import prisma from "../../../../handlers/prisma";
import checkPermission from "../../../../utils/checkPermission";
import deferReply from "../../../../utils/deferReply";
import sendResponse from "../../../../utils/sendResponse";
export const builder = (command: SlashCommandSubcommandBuilder) => {
return command
.setName("quotes")
.setDescription(`Configure quotes module`)
.addBooleanOption((option) =>
option.setName("status").setDescription("Status").setRequired(true)
)
.addChannelOption((option) =>
option.setName("channel").setDescription("channel").setRequired(true)
);
};
export const execute = async (interaction: ChatInputCommandInteraction) => {
await deferReply(interaction, true);
checkPermission(interaction, PermissionsBitField.Flags.ManageGuild);
const { guild, options, user } = interaction;
const quoteStatus = options.getBoolean("status", true);
const quoteChannel = options.getChannel("channel", true);
if (!guild) {
throw new Error("Guild not found.");
}
const upsertGuildQuotesSettings = await prisma.guildQuotesSettings.upsert({
where: {
id: guild.id,
},
update: {
quoteChannelId: quoteChannel.id,
status: quoteStatus,
},
create: {
id: guild.id,
quoteChannelId: quoteChannel.id,
status: quoteStatus,
guildSettings: {
connectOrCreate: {
where: {
id: guild.id,
},
create: {
id: guild.id,
},
},
},
},
});
const embedSuccess = new EmbedBuilder()
.setAuthor({
name: "Configuration of Quotes",
})
.setColor(process.env.EMBED_COLOR_SUCCESS)
.setFooter({
text: `Successfully configured by ${user.username}`,
iconURL: user.displayAvatarURL(),
})
.setTimestamp();
await sendResponse(interaction, {
embeds: [
embedSuccess
.setDescription("Configuration updated successfully!")
.addFields({
name: "Status",
value: `${upsertGuildQuotesSettings.status}`,
inline: true,
})
.addFields({
name: "Channel ID",
value: `${upsertGuildQuotesSettings.quoteChannelId}`,
inline: true,
}),
],
});
};