Add allowUserClose option. Fixes #69

master
Dragory 2018-09-20 23:31:14 +03:00
parent 2f1b51d97a
commit 21d2a7453c
5 changed files with 79 additions and 39 deletions

View File

@ -52,6 +52,7 @@ const defaultConfig = {
"status": "Message me for help!", "status": "Message me for help!",
"responseMessage": "Thank you for your message! Our mod team will reply to you here as soon as possible.", "responseMessage": "Thank you for your message! Our mod team will reply to you here as soon as possible.",
"closeMessage": null, "closeMessage": null,
"allowUserClose": false,
"newThreadCategoryId": null, "newThreadCategoryId": null,
"mentionRole": "here", "mentionRole": "here",

View File

@ -204,7 +204,7 @@ class Thread {
} catch (e) { } catch (e) {
// Channel not found // Channel not found
if (e.code === 10003) { if (e.code === 10003) {
console.log(`[INFO] Auto-closing thread with ${this.user_name} because the channel no longer exists`); console.log(`[INFO] Failed to send message to thread channel for ${this.user_name} because the channel no longer exists. Auto-closing the thread.`);
this.close(true); this.close(true);
} else { } else {
throw e; throw e;
@ -319,7 +319,7 @@ class Thread {
async close(silent = false) { async close(silent = false) {
if (! silent) { if (! silent) {
console.log(`Closing thread ${this.id}`); console.log(`Closing thread ${this.id}`);
await this.postToThreadChannel('Closing thread...'); await this.postSystemMessage('Closing thread...');
} }
// Update DB status // Update DB status

View File

@ -2,7 +2,7 @@ const Eris = require('eris');
const config = require('./config'); const config = require('./config');
const bot = require('./bot'); const bot = require('./bot');
const Queue = require('./queue'); const {messageQueue} = require('./queue');
const utils = require('./utils'); const utils = require('./utils');
const blocked = require('./data/blocked'); const blocked = require('./data/blocked');
const threads = require('./data/threads'); const threads = require('./data/threads');
@ -25,8 +25,6 @@ const alert = require('./modules/alert');
const attachments = require("./data/attachments"); const attachments = require("./data/attachments");
const {ACCIDENTAL_THREAD_MESSAGES} = require('./data/constants'); const {ACCIDENTAL_THREAD_MESSAGES} = require('./data/constants');
const messageQueue = new Queue();
// Once the bot has connected, set the status/"playing" message // Once the bot has connected, set the status/"playing" message
bot.on('ready', () => { bot.on('ready', () => {
bot.editStatus(null, {name: config.status}); bot.editStatus(null, {name: config.status});

View File

@ -2,12 +2,12 @@ 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');
const threadUtils = require('../threadUtils'); const utils = require('../utils');
const utils = require("../utils"); const threads = require('../data/threads');
const threads = require("../data/threads"); const blocked = require('../data/blocked');
const {messageQueue} = require('../queue');
module.exports = bot => { module.exports = bot => {
const addInboxServerCommand = (...args) => threadUtils.addInboxServerCommand(bot, ...args);
const humanizeDelay = (delay, opts = {}) => humanizeDuration(delay, Object.assign({conjunction: ' and '}, opts)); 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
@ -38,42 +38,71 @@ module.exports = bot => {
scheduledCloseLoop(); scheduledCloseLoop();
// Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel. // Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel.
addInboxServerCommand('close', async (msg, args, thread) => { bot.registerCommand('close', async (msg, args) => {
if (! thread) return; let thread, closedBy;
// Timed close if (msg.channel instanceof Eris.PrivateChannel) {
if (args.length) { // User is closing the thread by themselves (if enabled)
if (args[0].startsWith('c')) { if (! config.allowUserClose) return;
// Cancel timed close if (await blocked.isBlocked(msg.author.id)) return;
if (thread.scheduled_close_at) {
await thread.cancelScheduledClose(); thread = await threads.findOpenThreadByUserId(msg.author.id);
thread.postSystemMessage(`Cancelled scheduled closing`); if (! thread) return;
// We need to add this operation to the message queue so we don't get a race condition
// between showing the close command in the thread and closing the thread
await messageQueue.add(async () => {
thread.postSystemMessage('Thread closed by user, closing...');
await thread.close(true);
});
closedBy = 'the user';
} else {
// A staff member is closing the thread
if (! utils.messageIsOnInboxServer(msg)) return;
if (! utils.isStaff(msg.member)) return;
thread = await threads.findOpenThreadByChannelId(msg.channel.id);
if (! thread) return;
// Timed close
if (args.length) {
if (args[0].startsWith('c')) {
// Cancel timed close
if (thread.scheduled_close_at) {
await thread.cancelScheduledClose();
thread.postSystemMessage(`Cancelled scheduled closing`);
}
return;
} }
// Set a timed close
const delay = utils.convertDelayStringToMS(args.join(' '));
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.`);
return; return;
} }
// Set a timed close // Regular close
const delay = utils.convertDelayStringToMS(args.join(' ')); await thread.close();
if (delay === 0 || delay === null) { closedBy = msg.author.username;
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.`);
return;
} }
// Regular close if (config.closeMessage) {
if(config.closeMessage) await thread.postToUser(config.closeMessage).catch(() => {}); await thread.postToUser(config.closeMessage).catch(() => {});
await thread.close(); }
const logUrl = await thread.getLogUrl(); const logUrl = await thread.getLogUrl();
utils.postLog(utils.trimAll(` utils.postLog(utils.trimAll(`
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${msg.author.username} Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}
Logs: ${logUrl} Logs: ${logUrl}
`)); `));
}); });
@ -82,11 +111,12 @@ module.exports = bot => {
bot.on('channelDelete', async (channel) => { bot.on('channelDelete', async (channel) => {
if (! (channel instanceof Eris.TextChannel)) return; if (! (channel instanceof Eris.TextChannel)) return;
if (channel.guild.id !== utils.getInboxGuild().id) return; if (channel.guild.id !== utils.getInboxGuild().id) return;
const thread = await threads.findOpenThreadByChannelId(channel.id); const thread = await threads.findOpenThreadByChannelId(channel.id);
if (! thread) return; if (! thread) return;
console.log(`[INFO] Auto-closing thread with ${thread.user_name} because the channel was deleted`); console.log(`[INFO] Auto-closing thread with ${thread.user_name} because the channel was deleted`);
if(config.closeMessage) await thread.postToUser(config.closeMessage).catch(() => {}); if (config.closeMessage) await thread.postToUser(config.closeMessage).catch(() => {});
await thread.close(true); await thread.close(true);
const logUrl = await thread.getLogUrl(); const logUrl = await thread.getLogUrl();

View File

@ -5,8 +5,16 @@ class Queue {
} }
add(fn) { add(fn) {
this.queue.push(fn); const promise = new Promise(resolve => {
if (! this.running) this.next(); this.queue.push(async () => {
await Promise.resolve(fn());
resolve();
});
if (! this.running) this.next();
});
return promise;
} }
next() { next() {
@ -20,10 +28,13 @@ class Queue {
const fn = this.queue.shift(); const fn = this.queue.shift();
new Promise(resolve => { new Promise(resolve => {
// Either fn() completes or the timeout of 10sec is reached // Either fn() completes or the timeout of 10sec is reached
Promise.resolve(fn()).then(resolve); fn().then(resolve);
setTimeout(resolve, 10000); setTimeout(resolve, 10000);
}).then(() => this.next()); }).then(() => this.next());
} }
} }
module.exports = Queue; module.exports = {
Queue,
messageQueue: new Queue()
};