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

Update bot to use dynamic timestamps #56

Open
wants to merge 1 commit into
base: development
Choose a base branch
from
Open
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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"discord.js": "^12.5.1",
"dotenv": "^8.2.0",
"googleapis": "^39.2.0",
"luxon": "^1.27.0",
"mongoose": "^5.11.8",
"node-cron": "^2.0.3"
},
Expand Down
12 changes: 6 additions & 6 deletions src/constants/studySession.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const { getUTCFullDate, getUTCFullTime } = require("../utils/date");
const { getDynamicDateTime } = require("../utils/date");

// Study session's related messages
const STUDY_SESSION = {
CREATE: {
SUCCESS: (session) => ({
title: "STUDY SESSION",
content: "Study session has been registered successfully!",
description: `📆 ${getUTCFullDate(session.startDate, "date")} at ${getUTCFullDate(session.startDate, "time")} *(UTC)*\n🕑 Estimated length: ${session.estimatedLength} minutes.\n\n${getStudySessionText(session.message)}\n\n*If anybody wants to join the session, subscribe using the ⭐ button\nIf you want to cancel the session, delete the message used to create it*`,
description: `📆 ${getDynamicDateTime(session.startDate, 'long date time')}\n🕑 Estimated length: ${session.estimatedLength} minutes.\n\n${getStudySessionText(session.message)}\n\n*If anybody wants to join the session, subscribe using the ⭐ button\nIf you want to cancel the session, delete the message used to create it*`,
withAuthor: true,
}),
ERROR: (error) => ({
Expand Down Expand Up @@ -45,7 +45,7 @@ const STUDY_SESSION = {
content: "Here are the upcoming study sessions:\n*Make sure to check the time zones!*",
fields: sessions.map((session) => ({
name: `${session.author.username}'s study session`,
value: `*${getUTCFullDate(session.startDate)} UTC (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
value: `*${getDynamicDateTime(session.startDate, 'short date time')} (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
})),
}),
ERROR: (error) => ({
Expand All @@ -62,7 +62,7 @@ const STUDY_SESSION = {
content: `👋 Hey ${subscriber.username}, you successfully registered to <@${author.id}> study session! See you soon!`,
description: messageContent.substr(6).trim(),
}),
REMINDER: (studySession, subscriber) => ({ content: `👋 How is your day going, ${subscriber.username}? Thank you for waiting, <@${studySession.author.id}>'s study session is starting in an hour! See you on the Korean Study Group server at **${getUTCFullTime(studySession.startDate)} UTC**!\n*Make sure to check the time zone!*` }),
REMINDER: (studySession, subscriber) => ({ content: `👋 How is your day going, ${subscriber.username}? Thank you for waiting, <@${studySession.author.id}>'s study session is starting ${getDynamicDateTime(studySession.startDate, 'relative')}! See you on the Korean Study Group server at **${getDynamicDateTime(studySession.startDate, 'short time')}**!\n` }),
ERROR: (author, error) => ({
content: `${author.username}, you just tried to subscribe to a study session. Thanks for your participation! However, an error as occurred during the process. Please try again! (and don't hesitate to notify <@202787014502776832> about this error)`,
title: "❌ Subscription error",
Expand Down Expand Up @@ -138,7 +138,7 @@ function getStudySessionCancellationInformation(studySession) {
cancellationMessage = title;
}

return `${cancellationMessage} on ${getUTCFullDate(startDate, "date")} at ${getUTCFullDate(startDate, "time")} *(UTC)* has been cancelled`;
return `${cancellationMessage} on ${getDynamicDateTime(startDate, 'long date time')} has been cancelled`;
}

function getTitle(message) {
Expand All @@ -161,7 +161,7 @@ function getStudySessionCancellationSubscriberNotification(studySession) {
cancellationMessage = title;
}

return `${cancellationMessage} on ${getUTCFullDate(startDate, "date")} at ${getUTCFullDate(startDate, "time")} *(UTC)*`;
return `${cancellationMessage} on ${getDynamicDateTime(startDate, 'long date time')}`;
}

module.exports = { STUDY_SESSION };
6 changes: 3 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { addBookmark, removeBookmark } = require("./scripts/users/dm/bookmarks");
const { unPin50thMsg, getAllChannels, ping, handleHelpCommand } = require("./scripts/utilities");
const { typingGame, typingGameListener, endTypingGame, gameExplanation } = require("./scripts/activities/games");
const { createStudySession, getUpcomingStudySessions, cancelStudySessionFromCommand, cancelStudySessionFromDeletion, subscribeStudySession, unsubscribeStudySession, updateStudySessionDetails } = require("./scripts/activities/study-session");
const { convertBetweenTimezones } = require("./scripts/utility-commands/time-and-date");
const { createDynamicTime } = require("./scripts/utility-commands/time-and-date");
const { loadMessageReaction } = require("./utils/cache");
const runScheduler = require("./scheduler").default;
/* ------------------------------------------------------ */
Expand Down Expand Up @@ -151,8 +151,8 @@ client.on("message", (message) => {
return;
}

if (text.startsWith("!timezone")) {
convertBetweenTimezones(message);
if (text.startsWith("!time")) {
createDynamicTime(message);
return;
}
});
Expand Down
52 changes: 28 additions & 24 deletions src/scripts/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ function logMessageDate() {
}

function handleHelpCommand(message) {
if (message.content.startsWith('!help timezone')) {
handleHelpTimezoneCommand(message);
if (message.content.startsWith('!help time')) {
handleHelpTimeCommand(message);
return;
}
if (message.content.startsWith('!help cancel study')) {
Expand Down Expand Up @@ -119,11 +119,11 @@ You can cancel a study session either by deleting the message used to create the
`
},
{
name: 'Timezones',
name: 'Time',
value: `
Use \`!timezone\` to convert a date-time to equivalent date-times in different regions
Use \`!time\` to generate a dynamic date-time which shows the correct time to each user based on their local timezone

Use \`!help timezone\` for more information
Use \`!help time\` for more information
`
}, {
name: 'Bookmarks',
Expand All @@ -143,36 +143,40 @@ Any message the bot sends via DM can be deleted by applying an 'x' (❌) reactio
});
}

function handleHelpTimezoneCommand(message) {
function handleHelpTimeCommand(message) {
const currentTime = Math.round(new Date().getTime() / 1000);
message.channel.send(null, {
embed: {
title: "The !timezone command",
title: "The !time command",
fields: [
{
name: 'Description',
value: 'The `!timezone` command can be used to convert a date-time into date-times around the world'
value: 'The `!time` command can be used to generate a dynamic date-time'
}, {
name: 'Format',
value: `
This requires a date in the format YYYY/MM/DD and a UTC time in HH:mm followed by any number of [TZ database names](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List)
This command optionally takes a UTC date-time in the format YYYY/MM/DD HH:mm and an output type

If a date is not supplied it will use the current date and time
If an output type is not supplied it will use the default output type ("short date time")
`
}, {
name: 'Example',
name: 'Examples',
value: `
If you've created a study session with these details
\`\`\`
!study 2022/03/05 at 13:30 ...
\`\`\`
You can get the equivalent date-times in Toronto, London and Seoul using
\`\`\`
!timezone 2022/03/05 13:30 America/Toronto Europe/London Asia/Seoul
\`\`\`
Which creates this response:
\`\`\`
Sat, 5 Mar at 08:30 (Eastern Standard Time)
Sat, 5 Mar at 13:30 (Greenwich Mean Time)
Sat, 5 Mar at 22:30 (Korean Standard Time)
\`\`\`
\`!time\` on its own will generate <t:${currentTime}:f>
\`!time short time\` will generate <t:${currentTime}:t>
\`!time 2021/07/29 22:00 relative\` will generate <t:1627592400:R>
`
}, {
name: 'Output types',
value: `
short date: <t:${currentTime}:d>
long date: <t:${currentTime}:D>
short time: <t:${currentTime}:t>
long time: <t:${currentTime}:T>
short date time: <t:${currentTime}:f>
long date time: <t:${currentTime}:F>
relative: <t:${currentTime}:R>
`
}
]
Expand Down
47 changes: 16 additions & 31 deletions src/scripts/utility-commands/time-and-date.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
const { DateTime } = require("luxon");
const { getDynamicDateTime, DATE_FORMAT } = require("../../utils/date");

function convertBetweenTimezones(message) {
let text = message.content ?? "";
if (text.length < 10) {
return;
}
text = text.substring(9).trim();
const COMMAND = "!time";

const dateTime = getDateTimeFromMessage(text);
if (dateTime === null) {
message.reply("please provide a valid datetime in the format YYYY/MM/DD HH:mm");
return;
}

const timezones = getTimeZonesFromMessage(text);
if (timezones === null || timezones.length === 0) {
message.reply("please provide at least one valid timezone to convert to. \n\nFor more information use `!help timezone`");
function createDynamicTime(message) {
let text = message.content ?? "";
if (text.length < COMMAND.length) {
return;
}

let convertedTimes = [];
timezones.forEach(timezone => {
const convertedTime = dateTime.setZone(timezone);
if (convertedTime.invalid === null) {
const timeZoneAbbreviation = convertedTime.offsetNameLong;
const dateTimeString = `${convertedTime.weekdayShort}, ${convertedTime.day} ${convertedTime.monthShort} at ${convertedTime.toFormat('HH:mm')} *(${timeZoneAbbreviation})*`;
convertedTimes.push(dateTimeString);
}
});
text = text.substring(5).trim();
Fox-Islam marked this conversation as resolved.
Show resolved Hide resolved
const dateTime = getDateTimeFromMessage(text) ?? new Date();

message.channel.send(convertedTimes.join('\n'));
const dynamicDateTimeString = getDynamicDateTime(dateTime, getFormat(text));
message.channel.send(`${dynamicDateTimeString}\n\nAdd this dynamic time to your messages using \`${dynamicDateTimeString}\`\nUse \`!help time\` for more information.`);
}

function getDateTimeFromMessage(text) {
Expand All @@ -41,12 +24,14 @@ function getDateTimeFromMessage(text) {
if (isNaN(jsDate.getTime())) {
return null;
}
return DateTime.fromISO(jsDate.toISOString());
return jsDate;
}

function getTimeZonesFromMessage(text) {
const timezoneRegex = /([A-Za-z]+[\/][A-Za-z0-9_\-+]*)/g;
return text.match(timezoneRegex);
function getFormat(text) {
const keys = Object.keys(DATE_FORMAT);
const formatPattern = new RegExp(`(${keys.join('|').replaceAll('_', ' ')})`);
const maybeFormat = text.toUpperCase().match(formatPattern);
return maybeFormat ? maybeFormat[0] : null;
}

module.exports = { convertBetweenTimezones };
module.exports = { createDynamicTime };
4 changes: 2 additions & 2 deletions src/tasks/studySession/channelReminder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { getUpcomingStudySessionsForScheduler } = require('../../scripts/activities/study-session');
const { getUTCFullDate } = require("../../utils/date");
const { getDynamicDateTime } = require("../../utils/date");

const upcomingStudySessionMessageContent = "Here are the upcoming study sessions:\n*Make sure to check the time zones!*";
const oneHour = 60 * 60 * 1000;
Expand Down Expand Up @@ -69,7 +69,7 @@ function makeStudySessionMessage() {
title: "UPCOMING STUDY SESSIONS",
fields: upcomingStudySessions.map((session) => ({
name: `${session.author.username}'s study session`,
value: `*${getUTCFullDate(session.startDate)} UTC (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
value: `*${getDynamicDateTime(session.startDate, 'short date time')} (${session.estimatedLength} min)*\n${getUpcomingStudySessionSummary(session.message)} - Subscribe [here](${session.message?.link})`,
})),
color: "GREEN"
}
Expand Down
44 changes: 18 additions & 26 deletions src/utils/date.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,23 @@
function getUTCFullDate(date, separateIntoGroups = "") {
if (!date) return "";
const dateUTC = date.toUTCString();
const DATE_FORMAT = Object.freeze({
SHORT_DATE_TIME: 'f',
LONG_DATE_TIME: 'F',
SHORT_DATE: 'd',
LONG_DATE: 'D',
SHORT_TIME: 't',
LONG_TIME: 'T',
RELATIVE: 'R'
});

// Optionally separate date, year, time, and timezone into groups
const separateRegEx = /(?<onlyDate>\w{3},\s\d+\s\w{3})\s(?<year>\d{4})\s(?<time>\d+:\d+).+(?<zone>\w{3})/;
const { onlyDate, year, time, zone } = dateUTC.match(separateRegEx).groups;
switch (separateIntoGroups) {
case "date":
return onlyDate;
case "year":
return year;
case "time":
return time;
case "zone":
return zone;
}

return dateUTC.substr(0, dateUTC.length - 7); // Remove seconds and GMT string
function getDynamicDateTime(date, outputFormat) {
const format = outputFormat ? DATE_FORMAT[outputFormat.toUpperCase().replaceAll(' ', '_')] : 'f'
return `<t:${removeMilliseconds(date)}:${format}>`
}

function getUTCFullTime(date) {
if (!date) return "";
const hour = date.getUTCHours();
const min = date.getUTCMinutes();
const fullHour = hour >= 10 ? hour : `0${hour}`;
const fullMin = min > 10 ? min : `0${min}`;
return `${fullHour}:${fullMin}`;
/**
* @description JavaScript includes milliseconds but Discord's dynamic datetime feature only supports up to second precision
*/
function removeMilliseconds(date) {
return Math.round(date.getTime() / 1000);
}

module.exports = { getUTCFullDate, getUTCFullTime };
module.exports = { getDynamicDateTime, DATE_FORMAT };