Skip to content

Commit

Permalink
feat: Add reviews commands to the bot
Browse files Browse the repository at this point in the history
Signed-off-by: Michal Drla <[email protected]>
  • Loading branch information
mimotej committed Dec 14, 2023
1 parent b25503c commit 38ac3bb
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/buttons/dislikeButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ButtonBuilder, ButtonInteraction, ButtonStyle } from 'discord.js';
import { prisma } from '../model';

export const data = new ButtonBuilder()
.setCustomId('Dislike')
.setLabel('👎')
.setStyle(ButtonStyle.Primary);

export async function execute(interaction: ButtonInteraction) {
const prevEmbed = interaction.message.embeds[0];
const reviewId = prevEmbed.footer?.text.split(' ').pop();
await prisma.reviews.update({
where: {
id: reviewId,
},
data: {
negativeRating: {
increment: 1,
},
},
});
return interaction.update({
content: 'Thank you for your vote',
});
}
4 changes: 4 additions & 0 deletions src/buttons/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * as addChannel from './addChannelButton';
export * as openCreateModal from './openCreateModalButton';
export * as Like from './likeButton';
export * as Dislike from './dislikeButton';
export * as PreviousReview from './previousEmbedButton';
export * as NextReview from './nextEmbedButton';
25 changes: 25 additions & 0 deletions src/buttons/likeButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ButtonBuilder, ButtonInteraction, ButtonStyle } from 'discord.js';
import { prisma } from '../model';

export const data = new ButtonBuilder()
.setCustomId('Like')
.setLabel('👍')
.setStyle(ButtonStyle.Primary);

export async function execute(interaction: ButtonInteraction) {
const prevEmbed = interaction.message.embeds[0];
const reviewId = prevEmbed.footer?.text.split(' ').pop();
await prisma.reviews.update({
where: {
id: reviewId,
},
data: {
positiveRating: {
increment: 1,
},
},
});
return interaction.update({
content: 'Thank you for your vote',
});
}
23 changes: 23 additions & 0 deletions src/buttons/nextEmbedButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ButtonBuilder, ButtonInteraction, ButtonStyle } from 'discord.js';
import { embedContentBuilder } from '../utils/embedContentBuilder';
import { reviewEmbedConstructor } from '../embeds';

export const data = new ButtonBuilder()
.setCustomId('NextReview')
.setLabel('Next')
.setStyle(ButtonStyle.Primary);

export async function execute(interaction: ButtonInteraction) {
const move = 1;
const content = await embedContentBuilder(interaction, move);
if (content == undefined) {
return interaction.update({
content: 'Error while processing reviews!',
});
}
const embed = reviewEmbedConstructor(content);
return interaction.update({
content: '',
embeds: [embed],
});
}
23 changes: 23 additions & 0 deletions src/buttons/previousEmbedButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ButtonBuilder, ButtonInteraction, ButtonStyle } from 'discord.js';
import { embedContentBuilder } from '../utils/embedContentBuilder';
import { reviewEmbedConstructor } from '../embeds';

export const data = new ButtonBuilder()
.setCustomId('PreviousReview')
.setLabel('Previous')
.setStyle(ButtonStyle.Primary);

export async function execute(interaction: ButtonInteraction) {
const move = -1;
const content = await embedContentBuilder(interaction, move);
if (content == undefined) {
return interaction.update({
content: 'Error while processing reviews!',
});
}
const embed = reviewEmbedConstructor(content);
return interaction.update({
content: '',
embeds: [embed],
});
}
1 change: 1 addition & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * as config from './config';
export * as channelmanagement from './channelmanagement';
export * as edit from './edit';
export * as channelrole from './channelrole';
export * as review from './review';
139 changes: 139 additions & 0 deletions src/commands/review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
ActionRowBuilder,
ButtonBuilder,
//ButtonInteraction,
ChatInputCommandInteraction,
SlashCommandBuilder,
} from 'discord.js';
import { prisma } from '../model';
import { reviewEmbedConstructor } from '../embeds';
import { Dislike, Like, NextReview, PreviousReview } from '../buttons';
import { Review } from '../utils';

/**
* Edits existing bot message in a channel.
*/
export const data = new SlashCommandBuilder()
.setName('review')
.setDescription('Interacts with review feature of the bot')
.addSubcommand((subcommand) =>
subcommand
.setName('get')
.setDescription('Outputs table with reviews for the subject')
.addStringOption((option) =>
option
.setName('subject')
.setDescription('Code of the subject (e.g. PV276)')
.setRequired(true)
)
)
.addSubcommand((subcommand) =>
subcommand
.setName('add')
.setDescription('Adds new review for the subjet')
.addStringOption((option) =>
option
.setName('subject')
.setDescription('Code of the subject (e.g. PV276)')
.setRequired(true)
)
.addStringOption((option) =>
option
.setName('text')
.setDescription('Text of the review')
.setRequired(true)
)
);

export async function execute(interaction: ChatInputCommandInteraction) {
await interaction.reply({
content: 'Processing review request...',
ephemeral: true,
});
const subjectCode = interaction.options.getString('subject');
if (interaction.options.getSubcommand() === 'add') {
const userId = interaction.user.id;
const textReview = interaction.options.getString('text');
if (textReview == undefined || subjectCode == undefined) {
console.log('Error with arguments for create review command.');
return interaction.editReply({
content: `Error while creating a review for ${subjectCode}.`,
});
}
const existingReview = await prisma.reviews.findMany({
where: {
discordUserId: {
equals: userId,
},
subjectCode: {
equals: subjectCode,
},
},
});
console.log(existingReview);
if (existingReview.length != 0) {
return interaction.editReply({
content: `You have already submitted review fro subject ${subjectCode}.`,
});
}
try {
await prisma.reviews.create({
data: {
discordUserId: userId,
subjectCode: subjectCode.toUpperCase(),
reviewText: textReview,
},
});
} catch (err) {
console.log(`Database error: ${err}`);
return interaction.editReply({
content: `Error while creating a review for ${subjectCode}.`,
});
}
return interaction.editReply({
content: `Review for subject ${subjectCode} created.`,
});
} else if (interaction.options.getSubcommand() === 'get') {
const submittedReviews = await prisma.reviews.findMany({
orderBy: {
dateReview: 'asc',
},
where: {
subjectCode: subjectCode?.toUpperCase(),
},
});
const title = `Reviews for course ${subjectCode}`;
const reviewer = await interaction.client.users.fetch(
submittedReviews[0].discordUserId
);
const review: Review = {
id: submittedReviews[0].id,
invokedUsername: interaction.user.username,
invokedAvatarUrl: interaction.user.displayAvatarURL(),
title: title,
description: `\`\`\`${submittedReviews[0].reviewText}\`\`\``,
positiveRating: submittedReviews[0].positiveRating,
negativeRating: submittedReviews[0].negativeRating,
reviewerUsername: reviewer.username,
reviwerAvatarUrl: reviewer.displayAvatarURL(),
reviewNumber: 1,
numberOfReviews: submittedReviews.length,
};
const reviewEmbed = reviewEmbedConstructor(review);
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
PreviousReview.data,
NextReview.data,
Like.data,
Dislike.data
);
return interaction.editReply({
content: '',
embeds: [reviewEmbed],
components: [row],
});
} else {
return interaction.editReply({
content: `Invalid command.`,
});
}
}
1 change: 1 addition & 0 deletions src/embeds/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './reviewsEmbed';
24 changes: 24 additions & 0 deletions src/embeds/reviewsEmbed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { EmbedBuilder } from 'discord.js';
import { Review } from '../utils';

export function reviewEmbedConstructor(reviewInfo: Review) {
const reviewEmbed = new EmbedBuilder()
.setColor('#2f00ff')
.setTitle(reviewInfo.title)
.setAuthor({
name: reviewInfo.invokedUsername,
iconURL: reviewInfo.invokedAvatarUrl,
})
.setDescription(reviewInfo.description)
.addFields({
name: 'Rating of the review',
value: `${reviewInfo.positiveRating}👍|${reviewInfo.negativeRating}👎`,
})
.setTimestamp()
.setFooter({
text: `Reviwer: ${reviewInfo.reviewerUsername} · Review number: ${reviewInfo.reviewNumber}/${reviewInfo.numberOfReviews} · ID: ${reviewInfo.id}`,
iconURL: reviewInfo.reviwerAvatarUrl,
});

return reviewEmbed;
}
10 changes: 10 additions & 0 deletions src/model/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ model Users {
status String
joinDate DateTime? @default(now())
}

model Reviews {
id String @id @default(auto()) @map("_id") @database.ObjectId
subjectCode String
reviewText String
discordUserId String
positiveRating Int @default(0)
negativeRating Int @default(0)
dateReview DateTime @default(now())
}
13 changes: 13 additions & 0 deletions src/utils/Review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface Review {
id: string;
invokedUsername: string;
invokedAvatarUrl: string;
title: string;
description: string;
positiveRating: number;
negativeRating: number;
reviewerUsername: string;
reviwerAvatarUrl: string;
reviewNumber: number;
numberOfReviews: number;
}
54 changes: 54 additions & 0 deletions src/utils/embedContentBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ButtonInteraction } from 'discord.js';
import { Review } from './Review';
import { prisma } from '../model';

export async function embedContentBuilder(
interaction: ButtonInteraction,
move: number
) {
const prevEmbed = interaction.message.embeds[0];
const reviewId = prevEmbed.footer?.text.split(' ').pop();
const subjectCode = prevEmbed.title?.replace('Reviews for course ', '');
const submittedReviews = await prisma.reviews.findMany({
orderBy: {
dateReview: 'asc',
},
where: {
subjectCode: subjectCode?.toUpperCase(),
},
});
if (prevEmbed.title == null) {
return;
}
for (let index = 0; index < submittedReviews.length; index++) {
if (submittedReviews[index].id === reviewId) {
if (index + move >= submittedReviews.length) {
index = -1;
}
if (index + move < 0) {
index = submittedReviews.length;
}
const reviewer = await interaction.client.users.fetch(
submittedReviews[index + move].discordUserId
);
const review: Review = {
id: submittedReviews[index + move].id,
invokedUsername: interaction.user.username,
invokedAvatarUrl: interaction.user.displayAvatarURL(),
title: prevEmbed.title,
description: `\`\`\`${
submittedReviews[index + move].reviewText
}\`\`\``,
positiveRating: submittedReviews[index + move].positiveRating,
negativeRating: submittedReviews[index + move].negativeRating,
reviewerUsername: reviewer.username,
reviwerAvatarUrl: reviewer.displayAvatarURL(),
reviewNumber: index + move + 1,
numberOfReviews: submittedReviews.length,
};
return review;
}
}
console.log('Error occured while processing reviews.');
return undefined;
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { Config, ConfigKey, ConfigValue, ConfigProperties } from './config';
export { Embed } from './embed';
export { SubjectChannels } from './setupSubjectChannels';
export { parseCustomId } from './parseCustomId';
export { Review } from './Review';

0 comments on commit 38ac3bb

Please sign in to comment.