Skip to content

Commit

Permalink
Added /dining menus command
Browse files Browse the repository at this point in the history
This commit adds the `/dining menus` command (a subcommand of `/dining`), which allows users to see menus for MIT dining venues.
Daily dining information is cached if the configuration option is enabled, and MongoDB should be set to delete them after 24 hours via its TTL function.
  • Loading branch information
ZelnickB committed Jan 18, 2025
1 parent 3151ed3 commit cf1d2dd
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 6 deletions.
68 changes: 68 additions & 0 deletions discordBot/commands/ChatInput/logic/dining.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import _ from 'lodash'
import { DateTime } from 'luxon'
import { EmbedBuilder } from 'discord.js'
import * as dining from '../../../../lib/publicAPIs/dining.js'

export default async function (interaction) {
if (interaction.options.getSubcommandGroup() === null) {
switch (interaction.options.getSubcommand()) {
case 'menus': {
const venue = interaction.options.getString('venue')
const meal = interaction.options.getString('meal')
const [, mealInfo] = await Promise.all([interaction.deferReply(), dining.getMealInfo(venue, meal)])
if (mealInfo === undefined) {
return interaction.editReply({
content: `**Error:** ${venue} is not open for ${meal.toLowerCase()} today, or the menu is not available.`
})
}
const menu = dining.parseMenu(mealInfo)
let embedColor = 0x808080
switch (meal) {
case 'Breakfast': {
embedColor = 0xff8000
break
}
case 'Brunch': {
embedColor = 0x00ff60
break
}
case 'Lunch': {
embedColor = 0x0080ff
break
}
case 'Dinner': {
embedColor = 0xc000ff
break
}
case 'Late Night': {
embedColor = 0x001060
break
}
}
const embed = new EmbedBuilder()
.setTitle(`${dining.getVenueFullName(venue)} ${meal} Menu`)
.setAuthor({
name: 'Massachusetts Institute of Technology Dining'
})
.setDescription(`${meal} is being served at ${venue} from ${mealInfo.start_time} to ${mealInfo.end_time} today.`)
.setFooter({
text: `Menu for ${meal} at ${venue} Dining, ${DateTime.now().setZone('America/New_York').toFormat('ccc d LLLL, yyyy')}.`
})
.setColor(embedColor)
for (const station in menu) {
if (['beverages', 'condiments'].includes(station.toLowerCase())) continue
embed.addFields({
name: _.upperFirst(station),
value: '* ' + menu[station].map(x => _.upperFirst(x.name)).join('\n* '),
inline: true
})
}
return interaction.editReply({
embeds: [embed]
})
}
}
} else {
return 0
}
}
74 changes: 74 additions & 0 deletions discordBot/commands/ChatInput/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,80 @@
}
]
},
{
"name": "dining",
"type": 1,
"description": "Retrieves up-to-date information about dining options at MIT.",
"options": [
{
"name": "menus",
"description": "Shows menus for MIT dining venues.",
"type": 1,
"options": [
{
"type": 3,
"name": "venue",
"required": true,
"description": "The venue for which menus should be retrieved.",
"choices": [
{
"name": "Maseeh Hall (W1)",
"value": "Maseeh"
},
{
"name": "McCormick Hall (W4)",
"value": "McCormick"
},
{
"name": "Baker House (W7)",
"value": "Baker"
},
{
"name": "New Vassar (W46)",
"value": "New Vassar"
},
{
"name": "Next House (W71)",
"value": "Next"
},
{
"name": "Simmons Hall (W79)",
"value": "Simmons"
}
]
},
{
"type": 3,
"name": "meal",
"required": true,
"description": "The meal for which the menu should be retrieved.",
"choices": [
{
"name": "Breakfast",
"value": "Breakfast"
},
{
"name": "Brunch",
"value": "Brunch"
},
{
"name": "Lunch",
"value": "Lunch"
},
{
"name": "Dinner",
"value": "Dinner"
},
{
"name": "Late Night",
"value": "Late Night"
}
]
}
]
}
]
},
{
"name": "idcard",
"type": 1,
Expand Down
62 changes: 62 additions & 0 deletions lib/publicAPIs/dining.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import _ from 'lodash'
import { DateTime } from 'luxon'
import { config } from '../preferencesReader.js'
import { dbClient } from '../mongoClient.js'

const cacheEnabled = (await config()).caching.dining
const diningCache = dbClient.collection('cache.dining')

export function getVenueFullName (shortName) {
switch (shortName) {
case 'Maseeh': return 'Maseeh Hall (W1)'
case 'McCormick': return 'McCormick Hall (W4)'
case 'Baker': return 'Baker Hall (W7)'
case 'New Vassar': return 'New Vassar (W46)'
case 'Next': return 'Next House (W71)'
case 'Simmons': return 'Simmons Hall (W79)'
}
}

export async function getTodayDiningInfo (withCache) {
if (withCache === undefined) withCache = cacheEnabled
let saveToCache = false
const currentDay = DateTime.now().setZone('America/New_York').startOf('day').toJSDate()
if (withCache) {
if (await diningCache.countDocuments({ _date: currentDay }) >= 1) {
return diningCache.findOne({ _date: currentDay })
} else {
saveToCache = true
}
}
return fetch('https://m.mit.edu/apis/dining/')
.then(response => response.json())
.then(async diningJSON => {
diningJSON = {
_date: currentDay,
...diningJSON
}
if (saveToCache) {
await diningCache.insertOne(diningJSON)
}
return diningJSON
})
}

export async function getMealInfo (venue, meal, withCache) {
const currentDay = DateTime.now().setZone('America/New_York')
const diningInfo = await getTodayDiningInfo(withCache)
let venueInfo = [...diningInfo.venues.house, ...diningInfo.venues.retail]
venueInfo = _.find(venueInfo, v => v.name === venue)
let mealInfo = _.find(venueInfo.meals_by_day, m => m.date === currentDay.toFormat('LL/dd/yyyy'))
mealInfo = _.find(mealInfo.meals, m => m.name === meal)
return mealInfo
}

export function parseMenu (mealInfo) {
if (mealInfo === undefined) return undefined
return _.groupBy(mealInfo.items, 'station')
}

export async function getMenu (venue, meal, withCache) {
return parseMenu(await getMealInfo(venue, meal, withCache))
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"discord.js": "^14.17.3",
"express": "^4.21.2",
"hbs": "^4.2.0",
"lodash": "^4.17.21",
"luxon": "^3.5.0",
"mongodb": "^6.12.0",
"openid-client": "^6.1.7",
Expand Down
9 changes: 3 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions samples/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ discord:
commandSettings:
directory:
enableAccountLinkStatusInDirectorySearch: true
caching:
dining: true
emoji:
aeroastro: "12345678901234567890"
affiliate: "12345678901234567890"
Expand Down

0 comments on commit cf1d2dd

Please sign in to comment.