From dc2c263f7a00d29b048f03129899dbdc5716c40d Mon Sep 17 00:00:00 2001 From: Fox Islam Date: Tue, 13 Jul 2021 16:28:04 +0100 Subject: [PATCH] Update bot to use dynamic timestamps Discord recently introduced dynamic in-line timestamps https://discord.com/developers/docs/reference#message-formatting-timestamp-styles which appear different to users based on their local timezone Updated the study session messages to use dynamic timezones to make scheduled event times clearer This also made the previously-introduced `!timezone` command obsolete - Replaced `!timezone` command with `!time` command which can be used by members to generate dynamic timestamps - Replaced `!help timezone` command with `!help time` command for additional information - Removed 'luxon' package, which was added only for the `!timezone` command --- package.json | 1 - src/constants/studySession.js | 12 ++--- src/index.js | 6 +-- src/scripts/utilities.js | 52 ++++++++++--------- src/scripts/utility-commands/time-and-date.js | 47 ++++++----------- src/tasks/studySession/channelReminder.js | 4 +- src/utils/date.js | 44 +++++++--------- 7 files changed, 73 insertions(+), 93 deletions(-) diff --git a/package.json b/package.json index 968035f..e870183 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/constants/studySession.js b/src/constants/studySession.js index 338d30d..589c799 100644 --- a/src/constants/studySession.js +++ b/src/constants/studySession.js @@ -1,4 +1,4 @@ -const { getUTCFullDate, getUTCFullTime } = require("../utils/date"); +const { getDynamicDateTime } = require("../utils/date"); // Study session's related messages const STUDY_SESSION = { @@ -6,7 +6,7 @@ const STUDY_SESSION = { 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) => ({ @@ -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) => ({ @@ -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", @@ -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) { @@ -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 }; diff --git a/src/index.js b/src/index.js index bd97784..a1ea7a8 100644 --- a/src/index.js +++ b/src/index.js @@ -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; /* ------------------------------------------------------ */ @@ -151,8 +151,8 @@ client.on("message", (message) => { return; } - if (text.startsWith("!timezone")) { - convertBetweenTimezones(message); + if (text.startsWith("!time")) { + createDynamicTime(message); return; } }); diff --git a/src/scripts/utilities.js b/src/scripts/utilities.js index e08d157..ab25332 100644 --- a/src/scripts/utilities.js +++ b/src/scripts/utilities.js @@ -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')) { @@ -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', @@ -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 +\`!time short time\` will generate +\`!time 2021/07/29 22:00 relative\` will generate + ` + }, { + name: 'Output types', + value: ` +short date: +long date: +short time: +long time: +short date time: +long date time: +relative: ` } ] diff --git a/src/scripts/utility-commands/time-and-date.js b/src/scripts/utility-commands/time-and-date.js index 6069422..f8cfb72 100644 --- a/src/scripts/utility-commands/time-and-date.js +++ b/src/scripts/utility-commands/time-and-date.js @@ -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(); + 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) { @@ -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 }; diff --git a/src/tasks/studySession/channelReminder.js b/src/tasks/studySession/channelReminder.js index 53dfe7a..fe5ae00 100644 --- a/src/tasks/studySession/channelReminder.js +++ b/src/tasks/studySession/channelReminder.js @@ -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; @@ -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" } diff --git a/src/utils/date.js b/src/utils/date.js index b145cbf..53f4261 100644 --- a/src/utils/date.js +++ b/src/utils/date.js @@ -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 = /(?\w{3},\s\d+\s\w{3})\s(?\d{4})\s(?