Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add reviews commands to the bot #101

Merged
merged 1 commit into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
144 changes: 144 additions & 0 deletions src/commands/review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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 from 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(),
},
});
if (submittedReviews.length == 0) {
return interaction.editReply({
content: `No reviews for ${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';