Skip to content

Commit 39e2bef

Browse files
authored
Merge branch 'develop' into dev/autoroledelete
2 parents 1d06cd5 + 36d43cd commit 39e2bef

18 files changed

+3828
-2187
lines changed

.eslintrc.json

Lines changed: 0 additions & 48 deletions
This file was deleted.

.github/workflows/docker.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
uses: actions/checkout@v4
1313
- uses: actions/setup-node@v4
1414
with:
15-
node-version: 16
15+
node-version: 20
1616
- run: npm ci
1717
- run: npm run format:check
1818
- run: npm run lint
@@ -64,7 +64,7 @@ jobs:
6464
repository: csesoc/deployment
6565
token: ${{ secrets.GH_TOKEN }}
6666
- name: Install yq - portable yaml processor
67-
uses: mikefarah/yq@v4.27.5
67+
uses: mikefarah/yq@v4.44.2
6868
- name: Determine file to update
6969
id: get_manifest
7070
env:

Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
# Build layer template for an eventual TS migration
2-
FROM node:20.13.1-slim as builder
2+
FROM node:20.15.0-slim as builder
33
ENV NODE_ENV=production
44

55
# Set working directory
66
WORKDIR /app
77

8+
RUN apt-get update && apt-get -y install libgbm1 && apt-get -y install libasound2
9+
810
# Install dependencies
911
COPY package.json package-lock.json ./
1012
RUN npm ci --omit=dev
1113

12-
FROM node:20.13.1-slim
14+
FROM node:20.15.0-slim
1315
ENV NODE_ENV=production
1416

1517
# Set working directory

commands/courseRating.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
const { SlashCommandBuilder } = require("@discordjs/builders");
2+
const { EmbedBuilder, AttachmentBuilder } = require("discord.js");
3+
const { ChartJSNodeCanvas } = require("chartjs-node-canvas");
4+
const puppeteer = require("puppeteer");
5+
6+
/**
7+
* Extracts the relevant information from the course page
8+
*/
9+
async function extractRating(url) {
10+
const browser = await puppeteer.launch();
11+
12+
const page = await browser.newPage();
13+
await page.goto(url, { waitUntil: "networkidle2" });
14+
15+
const courseTitle = await page.$eval(
16+
"h2.text-3xl.font-bold.break-words",
17+
(el) => el.textContent,
18+
);
19+
const numReviews = await page.$eval(".space-x-2 > span", (el) => el.textContent);
20+
const ratings = await page.$$eval(".flex.flex-wrap.justify-around > div", (items) => {
21+
const result = [];
22+
items.slice(0, 3).forEach((el) => {
23+
const rating = el.querySelector(".text-2xl.font-bold").textContent;
24+
const category = el.querySelector(".text-center.font-bold").textContent;
25+
result.push({
26+
name: category,
27+
value: `${rating} out of 5`,
28+
inline: true,
29+
});
30+
});
31+
return result;
32+
});
33+
34+
const fullDescription = await page.$eval(".whitespace-pre-line", (el) => el.textContent);
35+
const description = fullDescription.split(/(?<=[.!?])\s/)[0].trim();
36+
37+
await browser.close();
38+
return { courseTitle, numReviews, description, ratings };
39+
}
40+
41+
/**
42+
* Determines the color code based on the given rating.
43+
*
44+
* @param {number} rating - The rating value to evaluate.
45+
* @returns {string} - The corresponding color code in hexadecimal format.
46+
*
47+
*/
48+
function ratingColour(rating) {
49+
if (rating >= 3.5) {
50+
return "#39e75f";
51+
} else if (rating > 2.5) {
52+
return "#FFA500";
53+
}
54+
return "#FF0000";
55+
}
56+
57+
/**
58+
* Builds a doughnut chart representing the average rating from a list of ratings.
59+
*
60+
* @param {Array} ratings - An array of rating objects
61+
* @returns {Promise<Buffer>} - An image buffer of doughnut chart
62+
*/
63+
async function buildChart(ratings) {
64+
const width = 800;
65+
const height = 300;
66+
const averageRating =
67+
ratings.reduce((sum, rating) => {
68+
return sum + parseFloat(rating.value.split(" ")[0]);
69+
}, 0) / ratings.length;
70+
71+
const canvas = new ChartJSNodeCanvas({ width, height });
72+
73+
const config = {
74+
type: "doughnut",
75+
data: {
76+
datasets: [
77+
{
78+
data: [averageRating, 5 - averageRating],
79+
backgroundColor: [ratingColour(averageRating), "#e0e0e0"],
80+
borderJoinStyle: "round",
81+
borderRadius: [
82+
{
83+
outerStart: 20,
84+
innerStart: 20,
85+
},
86+
{
87+
outerEnd: 20,
88+
innerEnd: 20,
89+
},
90+
],
91+
borderWidth: 0,
92+
},
93+
],
94+
},
95+
options: {
96+
rotation: 290,
97+
circumference: 140,
98+
cutout: "88%",
99+
plugins: {
100+
legend: {
101+
display: false,
102+
},
103+
},
104+
},
105+
};
106+
107+
const image = await canvas.renderToBuffer(config);
108+
return image;
109+
}
110+
111+
module.exports = {
112+
data: new SlashCommandBuilder()
113+
.setName("courserating")
114+
.setDescription("Tells you the current rating of a specific course!")
115+
.addStringOption((option) =>
116+
option.setName("course").setDescription("Enter the course code").setRequired(true),
117+
),
118+
async execute(interaction) {
119+
const course = interaction.options.getString("course");
120+
121+
const url = `https://unilectives.devsoc.app/course/${course}`;
122+
123+
const year = new Date().getFullYear();
124+
const handbookUrl = `https://www.handbook.unsw.edu.au/undergraduate/courses/${year}/${course}`;
125+
126+
try {
127+
await interaction.deferReply({ ephemeral: true });
128+
129+
const { courseTitle, numReviews, description, ratings } = await extractRating(url);
130+
131+
if (numReviews == "0 reviews") {
132+
await interaction.editReply({
133+
content: "Sorry there are no reviews for this course yet 😔",
134+
});
135+
return;
136+
}
137+
138+
const image = await buildChart(ratings);
139+
const attachment = new AttachmentBuilder(image, { name: "rating.png" });
140+
ratings.unshift({
141+
name: "\u200B",
142+
value: `[${course} Handbook](${handbookUrl})`,
143+
});
144+
const replyEmbed = new EmbedBuilder()
145+
.setColor(0x0099ff)
146+
.setTitle(course + " " + courseTitle)
147+
.setURL(url)
148+
.setDescription(description)
149+
.setImage("attachment://rating.png")
150+
.addFields(ratings)
151+
.setFooter({ text: numReviews });
152+
153+
await interaction.editReply({ embeds: [replyEmbed], files: [attachment] });
154+
} catch (err) {
155+
console.log(err);
156+
await interaction.editReply({
157+
content: `Sorry the course could not be found! 😔`,
158+
});
159+
}
160+
},
161+
};

commands/wordle.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ module.exports = {
190190
for (let j = 0; j < 6; j++) {
191191
const imageNums = getAnswer(answer, guesses[j]);
192192
for (let i = 0; i < 5; i++) {
193-
// eslint-disable-next-line no-undef
194193
const imageNumber = imageNums[i];
195194
square = square_arr[imageNumber];
196195

config/lunch_buddy_locations.json

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
[
2+
{
3+
"value": "Upper Campus Food Court",
4+
"sub": [
5+
{
6+
"name": "Tropical Green Pho"
7+
},
8+
{
9+
"name": "Pho House"
10+
},
11+
{
12+
"name": "Classic Kebab"
13+
},
14+
{
15+
"name": "Chinese Takeaway"
16+
},
17+
{
18+
"name": "Tori Sushi"
19+
},
20+
{
21+
"name": "Gradu-eat"
22+
},
23+
{
24+
"name": "The Little Marionette Cafe"
25+
},
26+
{
27+
"name": "Lhaksa Delight"
28+
},
29+
{
30+
"name": "Bioscience building Cafe (XS Espresso)"
31+
}
32+
]
33+
},
34+
{
35+
"value": "Subway Zone",
36+
"sub": [
37+
{
38+
"name": "Subway"
39+
},
40+
{
41+
"name": "Boost"
42+
},
43+
{
44+
"name": "Southern Wok"
45+
},
46+
{
47+
"name": "Cafe Brioso"
48+
},
49+
{
50+
"name": "Penny Lane"
51+
}
52+
]
53+
},
54+
{
55+
"value": "Quadrangle Food Court",
56+
"sub": [
57+
{
58+
"name": "Soul Origin"
59+
},
60+
{
61+
"name": "PappaRich"
62+
},
63+
{
64+
"name": "Nene Chicken"
65+
},
66+
{
67+
"name": "Plume Cafe"
68+
}
69+
]
70+
},
71+
{
72+
"value": "Lower Campus",
73+
"sub": [
74+
{
75+
"name": "Stellinis Pasta Bar"
76+
},
77+
{
78+
"name": "Guzman Y Gomez"
79+
},
80+
{
81+
"name": "Mamak Village"
82+
},
83+
{
84+
"name": "Yallah Eats Kebab and Shawarma"
85+
},
86+
{
87+
"name": "Sharetea"
88+
},
89+
{
90+
"name": "Maze Coffee & Food"
91+
},
92+
{
93+
"name": "Campus Village Cafe"
94+
},
95+
{
96+
"name": "Home Ground Kiosk"
97+
}
98+
]
99+
},
100+
{
101+
"value": "J17 Ainsworth",
102+
"sub": [
103+
{
104+
"name": "Coffee on Campus Cafe"
105+
}
106+
]
107+
},
108+
{
109+
"value": "Other Options",
110+
"sub": [
111+
{
112+
"name": "Sport"
113+
},
114+
{
115+
"name": "On Campus Study"
116+
}
117+
]
118+
}
119+
]

config/wordle.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"players": []
3-
}
3+
}

0 commit comments

Comments
 (0)