Add support for scheduled !suspend

master
Dragory 2019-03-06 21:37:36 +02:00
parent bee147c19d
commit 688ab7ceea
6 changed files with 127 additions and 7 deletions

View File

@ -0,0 +1,15 @@
exports.up = async function(knex, Promise) {
await knex.schema.table('threads', table => {
table.dateTime('scheduled_suspend_at').index().nullable().defaultTo(null).after('channel_id');
table.string('scheduled_suspend_id', 20).nullable().defaultTo(null).after('channel_id');
table.string('scheduled_suspend_name', 128).nullable().defaultTo(null).after('channel_id');
});
};
exports.down = async function(knex, Promise) {
await knex.schema.table('threads', table => {
table.dropColumn('scheduled_suspend_at');
table.dropColumn('scheduled_suspend_id');
table.dropColumn('scheduled_suspend_name');
});
};

View File

@ -356,6 +356,7 @@ class Thread {
/** /**
* @param {String} time * @param {String} time
* @param {Eris~User} user * @param {Eris~User} user
* @param {Number} silent
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async scheduleClose(time, user, silent) { async scheduleClose(time, user, silent) {
@ -390,7 +391,10 @@ class Thread {
await knex('threads') await knex('threads')
.where('id', this.id) .where('id', this.id)
.update({ .update({
status: THREAD_STATUS.SUSPENDED status: THREAD_STATUS.SUSPENDED,
scheduled_suspend_at: null,
scheduled_suspend_id: null,
scheduled_suspend_name: null
}); });
} }
@ -405,6 +409,34 @@ class Thread {
}); });
} }
/**
* @param {String} time
* @param {Eris~User} user
* @returns {Promise<void>}
*/
async scheduleSuspend(time, user) {
await knex('threads')
.where('id', this.id)
.update({
scheduled_suspend_at: time,
scheduled_suspend_id: user.id,
scheduled_suspend_name: user.username
});
}
/**
* @returns {Promise<void>}
*/
async cancelScheduledSuspend() {
await knex('threads')
.where('id', this.id)
.update({
scheduled_suspend_at: null,
scheduled_suspend_id: null,
scheduled_suspend_name: null
});
}
/** /**
* @param {String} userId * @param {String} userId
* @returns {Promise<void>} * @returns {Promise<void>}

View File

@ -273,6 +273,18 @@ async function getThreadsThatShouldBeClosed() {
return threads.map(thread => new Thread(thread)); return threads.map(thread => new Thread(thread));
} }
async function getThreadsThatShouldBeSuspended() {
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss');
const threads = await knex('threads')
.where('status', THREAD_STATUS.OPEN)
.whereNotNull('scheduled_suspend_at')
.where('scheduled_suspend_at', '<=', now)
.whereNotNull('scheduled_suspend_at')
.select();
return threads.map(thread => new Thread(thread));
}
module.exports = { module.exports = {
findById, findById,
findOpenThreadByUserId, findOpenThreadByUserId,
@ -283,5 +295,6 @@ module.exports = {
getClosedThreadsByUserId, getClosedThreadsByUserId,
findOrCreateThreadForUser, findOrCreateThreadForUser,
getThreadsThatShouldBeClosed, getThreadsThatShouldBeClosed,
getThreadsThatShouldBeSuspended,
createThreadInDB createThreadInDB
}; };

View File

@ -1,4 +1,3 @@
const humanizeDuration = require('humanize-duration');
const moment = require('moment'); const moment = require('moment');
const Eris = require('eris'); const Eris = require('eris');
const config = require('../config'); const config = require('../config');
@ -8,8 +7,6 @@ const blocked = require('../data/blocked');
const {messageQueue} = require('../queue'); const {messageQueue} = require('../queue');
module.exports = bot => { module.exports = bot => {
const humanizeDelay = (delay, opts = {}) => humanizeDuration(delay, Object.assign({conjunction: ' and '}, opts));
// Check for threads that are scheduled to be closed and close them // Check for threads that are scheduled to be closed and close them
async function applyScheduledCloses() { async function applyScheduledCloses() {
const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed(); const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed();
@ -90,7 +87,6 @@ module.exports = bot => {
// Timed close // Timed close
const delayStringArg = args.find(arg => utils.delayStringRegex.test(arg)); const delayStringArg = args.find(arg => utils.delayStringRegex.test(arg));
if (delayStringArg) { if (delayStringArg) {
// Set a timed close
const delay = utils.convertDelayStringToMS(delayStringArg); const delay = utils.convertDelayStringToMS(delayStringArg);
if (delay === 0 || delay === null) { if (delay === 0 || delay === null) {
thread.postSystemMessage(`Invalid delay specified. Format: "1h30m"`); thread.postSystemMessage(`Invalid delay specified. Format: "1h30m"`);
@ -102,9 +98,9 @@ module.exports = bot => {
let response; let response;
if (silentClose) { if (silentClose) {
response = `Thread is now scheduled to be closed silently in ${humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`; response = `Thread is now scheduled to be closed silently in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`;
} else { } else {
response = `Thread is now scheduled to be closed in ${humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`; response = `Thread is now scheduled to be closed in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`;
} }
thread.postSystemMessage(response); thread.postSystemMessage(response);

View File

@ -1,11 +1,70 @@
const moment = require('moment');
const threadUtils = require('../threadUtils'); const threadUtils = require('../threadUtils');
const threads = require("../data/threads"); const threads = require("../data/threads");
const utils = require('../utils');
const config = require('../config');
const {THREAD_STATUS} = require('../data/constants');
module.exports = bot => { module.exports = bot => {
const addInboxServerCommand = (...args) => threadUtils.addInboxServerCommand(bot, ...args); const addInboxServerCommand = (...args) => threadUtils.addInboxServerCommand(bot, ...args);
// Check for threads that are scheduled to be suspended and suspend them
async function applyScheduledSuspensions() {
const threadsToBeSuspended = await threads.getThreadsThatShouldBeSuspended();
for (const thread of threadsToBeSuspended) {
if (thread.status === THREAD_STATUS.OPEN) {
await thread.suspend();
await thread.postSystemMessage(`**Thread suspended** as scheduled by ${thread.scheduled_suspend_name}. This thread will act as closed until unsuspended with \`!unsuspend\``);
}
}
}
async function scheduledSuspendLoop() {
try {
await applyScheduledSuspensions();
} catch (e) {
console.error(e);
}
setTimeout(scheduledSuspendLoop, 2000);
}
scheduledSuspendLoop();
addInboxServerCommand('suspend', async (msg, args, thread) => { addInboxServerCommand('suspend', async (msg, args, thread) => {
if (! thread) return; if (! thread) return;
if (args.length) {
// Cancel timed suspend
if (args.includes('cancel') || args.includes('c')) {
// Cancel timed suspend
if (thread.scheduled_suspend_at) {
await thread.cancelScheduledSuspend();
thread.postSystemMessage(`Cancelled scheduled suspension`);
}
return;
}
// Timed suspend
const delayStringArg = args.find(arg => utils.delayStringRegex.test(arg));
if (delayStringArg) {
const delay = utils.convertDelayStringToMS(delayStringArg);
if (delay === 0 || delay === null) {
thread.postSystemMessage(`Invalid delay specified. Format: "1h30m"`);
return;
}
const suspendAt = moment.utc().add(delay, 'ms');
await thread.scheduleSuspend(suspendAt.format('YYYY-MM-DD HH:mm:ss'), msg.author);
thread.postSystemMessage(`Thread will be suspended in ${utils.humanizeDelay(delay)}. Use \`${config.prefix}suspend cancel\` to cancel.`);
return;
}
}
await thread.suspend(); await thread.suspend();
thread.postSystemMessage(`**Thread suspended!** This thread will act as closed until unsuspended with \`!unsuspend\``); thread.postSystemMessage(`**Thread suspended!** This thread will act as closed until unsuspended with \`!unsuspend\``);
}); });

View File

@ -1,6 +1,7 @@
const Eris = require('eris'); const Eris = require('eris');
const bot = require('./bot'); const bot = require('./bot');
const moment = require('moment'); const moment = require('moment');
const humanizeDuration = require('humanize-duration');
const publicIp = require('public-ip'); const publicIp = require('public-ip');
const attachments = require('./data/attachments'); const attachments = require('./data/attachments');
const config = require('./config'); const config = require('./config');
@ -293,6 +294,8 @@ function isSnowflake(str) {
return snowflakeRegex.test(str); return snowflakeRegex.test(str);
} }
const humanizeDelay = (delay, opts = {}) => humanizeDuration(delay, Object.assign({conjunction: ' and '}, opts));
module.exports = { module.exports = {
BotError, BotError,
@ -324,4 +327,6 @@ module.exports = {
setDataModelProps, setDataModelProps,
isSnowflake, isSnowflake,
humanizeDelay,
}; };