diff --git a/db/migrations/20190306204728_add_scheduled_close_silent_to_threads.js b/db/migrations/20190306204728_add_scheduled_close_silent_to_threads.js new file mode 100644 index 0000000..de113e3 --- /dev/null +++ b/db/migrations/20190306204728_add_scheduled_close_silent_to_threads.js @@ -0,0 +1,11 @@ +exports.up = async function(knex, Promise) { + await knex.schema.table('threads', table => { + table.integer('scheduled_close_silent').nullable(); + }); +}; + +exports.down = async function(knex, Promise) { + await knex.schema.table('threads', table => { + table.dropColumn('scheduled_close_silent'); + }); +}; diff --git a/src/data/Thread.js b/src/data/Thread.js index ecdf593..0756c52 100644 --- a/src/data/Thread.js +++ b/src/data/Thread.js @@ -19,6 +19,7 @@ const {THREAD_MESSAGE_TYPE, THREAD_STATUS} = require('./constants'); * @property {String} scheduled_close_at * @property {String} scheduled_close_id * @property {String} scheduled_close_name + * @property {Number} scheduled_close_silent * @property {String} alert_id * @property {String} created_at */ @@ -326,10 +327,15 @@ class Thread { /** * @returns {Promise} */ - async close(noSystemMessage = false) { - if (! noSystemMessage) { + async close(suppressSystemMessage = false, silent = false) { + if (! suppressSystemMessage) { console.log(`Closing thread ${this.id}`); - await this.postSystemMessage('Closing thread...'); + + if (silent) { + await this.postSystemMessage('Closing thread silently...'); + } else { + await this.postSystemMessage('Closing thread...'); + } } // Update DB status @@ -352,13 +358,14 @@ class Thread { * @param {Eris~User} user * @returns {Promise} */ - async scheduleClose(time, user) { + async scheduleClose(time, user, silent) { await knex('threads') .where('id', this.id) .update({ scheduled_close_at: time, scheduled_close_id: user.id, - scheduled_close_name: user.username + scheduled_close_name: user.username, + scheduled_close_silent: silent }); } @@ -371,7 +378,8 @@ class Thread { .update({ scheduled_close_at: null, scheduled_close_id: null, - scheduled_close_name: null + scheduled_close_name: null, + scheduled_close_silent: null }); } diff --git a/src/modules/close.js b/src/modules/close.js index 9e92239..4abf96d 100644 --- a/src/modules/close.js +++ b/src/modules/close.js @@ -14,8 +14,11 @@ module.exports = bot => { async function applyScheduledCloses() { const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed(); for (const thread of threadsToBeClosed) { - if(config.closeMessage) await thread.postToUser(config.closeMessage).catch(() => {}); - await thread.close(); + if (config.closeMessage && ! thread.scheduled_close_silent) { + await thread.postToUser(config.closeMessage).catch(() => {}); + } + + await thread.close(false, thread.scheduled_close_silent); const logUrl = await thread.getLogUrl(); utils.postLog(utils.trimAll(` @@ -41,7 +44,8 @@ module.exports = bot => { bot.registerCommand('close', async (msg, args) => { let thread, closedBy; - let sendCloseMessage = !! config.closeMessage; + let hasCloseMessage = !! config.closeMessage; + let silentClose = false; if (msg.channel instanceof Eris.PrivateChannel) { // User is closing the thread by themselves (if enabled) @@ -67,7 +71,6 @@ module.exports = bot => { thread = await threads.findOpenThreadByChannelId(msg.channel.id); if (! thread) return; - // Timed close if (args.length) { if (args.includes('cancel') || args.includes('c')) { // Cancel timed close @@ -77,30 +80,46 @@ module.exports = bot => { } return; - } else if (args.includes('silent') || args.includes('s')) { - sendCloseMessage = false; - } else { + } + + // Silent close (= no close message) + if (args.includes('silent') || args.includes('s')) { + silentClose = true; + } + + // Timed close + const delayStringArg = args.find(arg => utils.delayStringRegex.test(arg)); + if (delayStringArg) { // Set a timed close - const delay = utils.convertDelayStringToMS(args.join(' ')); + const delay = utils.convertDelayStringToMS(delayStringArg); if (delay === 0 || delay === null) { thread.postSystemMessage(`Invalid delay specified. Format: "1h30m"`); return; } const closeAt = moment.utc().add(delay, 'ms'); - await thread.scheduleClose(closeAt.format('YYYY-MM-DD HH:mm:ss'), msg.author); - thread.postSystemMessage(`Thread is now scheduled to be closed in ${humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`); + await thread.scheduleClose(closeAt.format('YYYY-MM-DD HH:mm:ss'), msg.author, silentClose ? 1 : 0); + + let response; + if (silentClose) { + response = `Thread is now scheduled to be closed silently in ${humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`; + } else { + response = `Thread is now scheduled to be closed in ${humanizeDelay(delay)}. Use \`${config.prefix}close cancel\` to cancel.`; + } + + thread.postSystemMessage(response); return; } } // Regular close - await thread.close(); + await thread.close(false, silentClose); closedBy = msg.author.username; } - if (sendCloseMessage) { + // Send close message (unless suppressed with a silent close) + if (hasCloseMessage && ! silentClose) { await thread.postToUser(config.closeMessage).catch(() => {}); } diff --git a/src/utils.js b/src/utils.js index 3751369..2a3ce6b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -220,19 +220,20 @@ function trimAll(str) { .join('\n'); } +const delayStringRegex = /^([0-9]+)(?:([dhms])[a-z]*)?/i; + /** * Turns a "delay string" such as "1h30m" to milliseconds * @param {String} str * @returns {Number} */ function convertDelayStringToMS(str) { - const regex = /^([0-9]+)\s*([dhms])?[a-z]*\s*/; let match; let ms = 0; str = str.trim(); - while (str !== '' && (match = str.match(regex)) !== null) { + while (str !== '' && (match = str.match(delayStringRegex)) !== null) { if (match[2] === 'd') ms += match[1] * 1000 * 60 * 60 * 24; else if (match[2] === 'h') ms += match[1] * 1000 * 60 * 60; else if (match[2] === 's') ms += match[1] * 1000; @@ -312,6 +313,7 @@ module.exports = { disableLinkPreviews, getSelfUrl, getMainRole, + delayStringRegex, convertDelayStringToMS, getInboxMention, postSystemMessageWithFallback,