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

Add reminder command #806

Open
wants to merge 1 commit into
base: master
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
12 changes: 12 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ Prints the ID of the current DM channel with the user
Shows the DM channel ID, DM message ID, and message link of the specified user reply.
`<number>` is the message number shown in front of staff replies in the thread channel.

### `!rem <HH:MM> <reason>`
Adds a reminder for a task. You will be pinged as soon as the time is reached.
The reminder is stored in the database so if you restart the bot, the reminders remain active.
After you set up a reminder, the bot confirms it and gives you the reminder ID.

**Example:** `!rem 18:40 Help this people`

### `!delrem <ID>`
Delete a reminder via its ID

**Example:** `!delrem 18`

## Anywhere on the inbox server
These commands can be used anywhere on the inbox server, even outside Modmail threads.

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"tmp": "^0.2.1",
"transliteration": "^2.3.5",
"uuid": "^9.0.1",
"yargs-parser": "^21.1.1"
"yargs-parser": "^21.1.1",
"cron": "^3.1.7"
},
"devDependencies": {
"eslint": "^8.49.0",
Expand Down
16 changes: 16 additions & 0 deletions src/data/migrations/20171223203915_create_tables.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ exports.up = async function(knex, Promise) {
table.dateTime("created_at").notNullable();
});
}

if (! await knex.schema.hasTable("reminders")) {
await knex.schema.createTable("reminders", table => {
table.increments('id').primary();
table.string('thread_id').notNullable();
table.string('user_id').notNullable();
table.timestamp('reminder_time').notNullable();
table.text('message').notNullable();
table.timestamp('created_at').defaultTo(knex.fn.now());
table.timestamp('updated_at').defaultTo(knex.fn.now());
});
}
};

exports.down = async function(knex, Promise) {
Expand All @@ -61,4 +73,8 @@ exports.down = async function(knex, Promise) {
if (await knex.schema.hasTable("snippets")) {
await knex.schema.dropTable("snippets");
}

if (await knex.schema.hasTable("reminders")) {
await knex.schema.dropTable("reminders");
}
};
1 change: 1 addition & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ function getBasePlugins() {
"file:./src/modules/joinLeaveNotification",
"file:./src/modules/roles",
"file:./src/modules/notes",
"file:./src/modules/reminder",
];
}

Expand Down
102 changes: 102 additions & 0 deletions src/modules/reminder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const { CronJob } = require('cron');
const threads = require("../data/threads");

const reminderJobs = {};

async function loadReminders(knex, bot) {
const reminders = await knex('reminders').select('*');
for (const reminder of reminders) {
const { id, thread_id, user_id, reminder_time, message } = reminder;
const alertTime = new Date(reminder_time);
const now = new Date();

if (alertTime > now) {
const cronTime = `${alertTime.getUTCMinutes()} ${alertTime.getUTCHours()} * * *`;
const job = new CronJob(cronTime, async () => {
const thread = await threads.findById(thread_id);
if (thread) {
await thread.postSystemMessage(`<@!${user_id}> Rappel : ${message}`);
}
job.stop();
delete reminderJobs[id];
}, null, true, 'UTC');

reminderJobs[id] = job;
}
}
}

module.exports = async ({ bot, knex, config, commands }) => {

await loadReminders(knex, bot);

commands.addInboxThreadCommand("rem", [{name: "message", type: "string", catchAll: true}], async (msg, args, thread) => {

const messageParts = args.message.split(" ");
const timePart = messageParts.shift();
const reminderMessage = messageParts.join(" ");

const timeMatch = timePart.match(/^(\d{1,2}):(\d{2})$/);
if (!timeMatch) {
await thread.postSystemMessage("Incorrect format. Use: `!rem HH:MM message`");
return;
}

const hour = parseInt(timeMatch[1], 10) - 2;
const minute = parseInt(timeMatch[2], 10);
const realHour = parseInt(timeMatch[1], 10);

if (isNaN(hour) || hour < 0 || hour > 23 || isNaN(minute) || minute < 0 || minute > 59) {
await thread.postSystemMessage("Invalid time. Use: `!rem HH:MM message`");
return;
}

const now = new Date();
let alertTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute);
let realAlertTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), realHour, minute);

if (alertTime < now) {
alertTime.setDate(alertTime.getDate() + 1);
}

const alertTimeUtc = alertTime.toISOString();

const [reminder] = await knex('reminders').insert({
thread_id: thread.id,
user_id: msg.author.id,
reminder_time: alertTimeUtc,
message: reminderMessage
}).returning('id');

const reminderId = reminder.id;

const cronTime = `${minute} ${hour} * * *`;
const job = new CronJob(cronTime, async () => {
await thread.postSystemMessage(`⏰ **REMINDER** <@${msg.author.id}> : \n\n >>> ${reminderMessage}`, {
allowedMentions: {
users: [msg.author.id],
},
});
job.stop();
delete reminderJobs[reminderId];
}, null, true, 'UTC');

reminderJobs[reminderId] = job;

await thread.postSystemMessage(`**⏰ Reminder set for ${realAlertTime.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })} :** \n\n > ${reminderMessage} \n\n **(ID: ${reminderId})**`);
}, { allowSuspended: true });

commands.addInboxThreadCommand("delrem", [{name: "id", type: "number"}], async (msg, args, thread) => {
await knex('reminders')
.where({ id: args.id, thread_id: thread.id })
.delete();

if (reminderJobs[args.id]) {
reminderJobs[args.id].stop();
delete reminderJobs[args.id];
}

await thread.postSystemMessage(`The reminder with ID **${args.id}** has been deleted.`);
}, { allowSuspended: true });

};