Skip to content

Commit

Permalink
Fixed recently graded assignments to use identifier cache and fixed r…
Browse files Browse the repository at this point in the history
…eply defer bug
  • Loading branch information
kartikk221 committed Feb 28, 2023
1 parent 5baa045 commit 586a43a
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 34 deletions.
52 changes: 51 additions & 1 deletion src/blackboard/client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as whenTime from 'when-time';
import { EventEmitter } from 'events';
import { sleep, with_retries } from '../utils.js';
import { with_retries } from '../utils.js';
import { generate_summary_embeds } from '../commands/summary.js';

export const MAX_KEEP_ALIVE_RETRIES = 5;
Expand All @@ -20,6 +20,7 @@ export class BlackboardClient extends EventEmitter {
token: null,
ignore: {},
alerts: {},
cache: {},
};

/**
Expand Down Expand Up @@ -166,6 +167,17 @@ export class BlackboardClient extends EventEmitter {
this.#client.token = null;
}

// Sanitize the cache to remove any expired data
let should_persist = false;
Object.keys(this.#client.cache).forEach((key) => {
const { expires_at } = this.#client.cache[key];
if (expires_at < Date.now()) {
should_persist = true;
delete this.#client.cache[key];
}
});
if (should_persist) this.emit('persist');

// Return a Boolean based on a valid user name was found
return is_logged_in;
}
Expand Down Expand Up @@ -244,6 +256,44 @@ export class BlackboardClient extends EventEmitter {
}
}

/**
* Sets a value in the cache with an expiration time.
* @param {string} key
* @param {any} value
* @param {number=} expires_in_ms Expires in milliseconds.
*/
set_in_cache(key, value, expires_in_ms = 1000 * 60 * 60 * 24 * 30) {
// Set the value in the cache
this.#client.cache[key] = {
value,
expires_at: Date.now() + expires_in_ms,
};

// Emit a 'persist' event
this.emit('persist');
}

/**
* Returns a value from the cache if it exists and has not expired.
* @param {string} key
* @returns {any}
*/
get_from_cache(key) {
// Get the value from the cache
const { value, expires_at } = this.#client.cache[key] || {};

// Return the value if it exists and has not expired
if (value && expires_at > Date.now()) {
return value;
} else if (expires_at < Date.now()) {
// Delete the value from the cache
delete this.#client.cache[key];

// Emit a 'persist' event
this.emit('persist');
}
}

/**
* @typedef {Object} Course
* @property {String} id The course ID.
Expand Down
80 changes: 49 additions & 31 deletions src/commands/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,37 +120,55 @@ export async function generate_summary_embeds(client, type, max_courses_age = In
// Filter assignments based on the specified type
const assignments = [];
for (let i = 0; i < names.length; i++) {
// Filter the assignments based on the specified type
const filtered = results[i]
.filter(({ status, deadline_at, updated_at }) => {
switch (type) {
case SUMMARY_TYPES.UPCOMING_ASSIGNMENTS:
// Filter out assignments that are not upcoming
// Filter out assignments whose due date is in the past
return status === 'UPCOMING' && deadline_at > Date.now();
case SUMMARY_TYPES.PAST_DUE_ASSIGNMENTS:
// Filter out assignments that are not upcoming
// Filter out assignments whose due date is in the future
// Filter out assignments that are older than 30 days
return (
status === 'UPCOMING' &&
deadline_at < Date.now() &&
Date.now() - deadline_at < 1000 * 60 * 60 * 24 * 30
);
case SUMMARY_TYPES.RECENTLY_GRADED_ASSIGNMENTS:
// Ensure the assignment has been graded
// Ensure the assignment was last updated within the last 24 hours
return status === 'GRADED' && Date.now() - updated_at < 1000 * 60 * 60 * 24 * 7;
}
})
.map((assignment) => {
// Include the course object in the assignment
assignment.course = courses[names[i]];
return assignment;
});

// Add the assignments to the array
assignments.push(...filtered);
// Check if assignments.scores were cached for this course
const course = courses[names[i]];
const identifier = `assignments.scores.${course.id}`;
const cached_scores = client.get_from_cache(identifier, names[i]);
if (cached_scores) {
// Filter the assignments based on the specified type
const filtered = results[i]
.filter(({ id, status, deadline_at, grade: { score } }) => {
switch (type) {
case SUMMARY_TYPES.UPCOMING_ASSIGNMENTS:
// Filter out assignments that are not upcoming
// Filter out assignments whose due date is in the past
return status === 'UPCOMING' && deadline_at > Date.now();
case SUMMARY_TYPES.PAST_DUE_ASSIGNMENTS:
// Filter out assignments that are not upcoming
// Filter out assignments whose due date is in the future
// Filter out assignments that are older than 30 days
return (
status === 'UPCOMING' &&
deadline_at < Date.now() &&
Date.now() - deadline_at < 1000 * 60 * 60 * 24 * 30
);
case SUMMARY_TYPES.RECENTLY_GRADED_ASSIGNMENTS:
// Ensure the assignment has been graded
// Ensure the assignment score is different from cached score
const cached_score = cached_scores[id];
return status === 'GRADED' && (!cached_score || cached_score !== score);
}
})
.map((assignment) => {
// Include the course object in the assignment
assignment.course = courses[names[i]];
return assignment;
});

// If there are some assignments, update the cache
if (filtered.length) {
filtered.forEach(({ id, grade: { score } }) => (cached_scores[id] = score));
client.set_in_cache(identifier, cached_scores, 1000 * 60 * 60 * 24 * 30 * 6); // 6 Months cache time
}

// Add the assignments to the array
assignments.push(...filtered);
} else {
// Cache the assignments.scores for this course
const assignment_scores = {};
results[i].forEach(({ id, grade: { score } }) => (assignment_scores[id] = score));
client.set_in_cache(identifier, assignment_scores, 1000 * 60 * 60 * 24 * 30 * 6); // 6 Months cache time
}
}

// Sort the assignments
Expand Down
4 changes: 2 additions & 2 deletions src/discord.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function on_client_interaction(interaction) {
ephemeral: true,
});
}
}, 2000);
}, 500);

// Inject a respond method into the interaction to simplify the command handlers
interaction.safe_reply = async (response) => {
Expand All @@ -70,7 +70,7 @@ export async function on_client_interaction(interaction) {
await deferred;

// Follow up with the interaction if it has been deferred
return interaction.followUp({
return interaction.editReply({
...response,
ephemeral: true,
});
Expand Down

0 comments on commit 586a43a

Please sign in to comment.