Add 'source' to beforeNewThread hooks, call hooks in threads.createNewThreadForUser()

cshd
Dragory 2020-07-15 23:50:30 +03:00
parent 75b2920777
commit 3723bf788b
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
6 changed files with 113 additions and 67 deletions

View File

@ -12,6 +12,7 @@ const utils = require('../utils');
const updates = require('./updates'); const updates = require('./updates');
const Thread = require('./Thread'); const Thread = require('./Thread');
const {callBeforeNewThreadHooks} = require("../hooks/beforeNewThread");
const {THREAD_STATUS} = require('./constants'); const {THREAD_STATUS} = require('./constants');
const MINUTES = 60 * 1000; const MINUTES = 60 * 1000;
@ -49,13 +50,17 @@ function getHeaderGuildInfo(member) {
}; };
} }
/**
* @typedef CreateNewThreadForUserOpts
* @property {boolean} quiet If true, doesn't ping mentionRole or reply with responseMessage
* @property {boolean} ignoreRequirements If true, creates a new thread even if the account doesn't meet requiredAccountAge
* @property {string} source A string identifying the source of the new thread
*/
/** /**
* Creates a new modmail thread for the specified user * Creates a new modmail thread for the specified user
* @param {User} user * @param {User} user
* @param {Object} opts * @param {CreateNewThreadForUserOpts} opts
* @param {Boolean} opts.quiet If true, doesn't ping mentionRole or reply with responseMessage
* @param {Boolean} opts.ignoreRequirements If true, creates a new thread even if the account doesn't meet requiredAccountAge
* @param {String} opts.categoryId Override the category ID for the new thread
* @returns {Promise<Thread|undefined>} * @returns {Promise<Thread|undefined>}
* @throws {Error} * @throws {Error}
*/ */
@ -68,6 +73,9 @@ async function createNewThreadForUser(user, opts = {}) {
throw new Error('Attempted to create a new thread for a user with an existing open thread!'); throw new Error('Attempted to create a new thread for a user with an existing open thread!');
} }
const hookResult = await callBeforeNewThreadHooks({ user, opts });
if (hookResult.cancelled) return;
// If set in config, check that the user's account is old enough (time since they registered on Discord) // If set in config, check that the user's account is old enough (time since they registered on Discord)
// If the account is too new, don't start a new thread and optionally reply to them with a message // If the account is too new, don't start a new thread and optionally reply to them with a message
if (config.requiredAccountAge && ! ignoreRequirements) { if (config.requiredAccountAge && ! ignoreRequirements) {
@ -131,7 +139,7 @@ async function createNewThreadForUser(user, opts = {}) {
console.log(`[NOTE] Creating new thread channel ${channelName}`); console.log(`[NOTE] Creating new thread channel ${channelName}`);
// Figure out which category we should place the thread channel in // Figure out which category we should place the thread channel in
let newThreadCategoryId = opts.categoryId || null; let newThreadCategoryId = hookResult.categoryId || null;
if (! newThreadCategoryId && config.categoryAutomation.newThreadFromGuild) { if (! newThreadCategoryId && config.categoryAutomation.newThreadFromGuild) {
// Categories for specific source guilds (in case of multiple main guilds) // Categories for specific source guilds (in case of multiple main guilds)
@ -340,11 +348,16 @@ async function getClosedThreadCountByUserId(userId) {
return parseInt(row.thread_count, 10); return parseInt(row.thread_count, 10);
} }
async function findOrCreateThreadForUser(user) { /**
* @param {User} user
* @param {CreateNewThreadForUserOpts} opts
* @returns {Promise<Thread|undefined>}
*/
async function findOrCreateThreadForUser(user, opts = {}) {
const existingThread = await findOpenThreadByUserId(user.id); const existingThread = await findOpenThreadByUserId(user.id);
if (existingThread) return existingThread; if (existingThread) return existingThread;
return createNewThreadForUser(user); return createNewThreadForUser(user, opts);
} }
async function getThreadsThatShouldBeClosed() { async function getThreadsThatShouldBeClosed() {

View File

@ -1,35 +0,0 @@
/**
* @callback BeforeNewThreadHook_SetCategoryId
* @param {String} categoryId
* @return void
*/
/**
* @typedef BeforeNewThreadHookEvent
* @property {Function} cancel
* @property {BeforeNewThreadHook_SetCategoryId} setCategoryId
*
*/
/**
* @callback BeforeNewThreadHook
* @param {BeforeNewThreadHookEvent} ev
* @return {void|Promise<void>}
*/
/**
* @type BeforeNewThreadHook[]
*/
const beforeNewThreadHooks = [];
/**
* @param {BeforeNewThreadHook} fn
*/
function beforeNewThread(fn) {
beforeNewThreadHooks.push(fn);
}
module.exports = {
beforeNewThreadHooks,
beforeNewThread,
};

View File

@ -0,0 +1,82 @@
const Eris = require('eris');
/**
* @callback BeforeNewThreadHook_SetCategoryId
* @param {String} categoryId
* @return void
*/
/**
* @typedef BeforeNewThreadHookData
* @property {Eris.User} user
* @property {CreateNewThreadForUserOpts} opts
* @property {Function} cancel
* @property {BeforeNewThreadHook_SetCategoryId} setCategoryId
*/
/**
* @typedef BeforeNewThreadHookResult
* @property {boolean} cancelled
* @property {string|null} categoryId
*/
/**
* @callback BeforeNewThreadHookData
* @param {BeforeNewThreadHookData} data
* @return {void|Promise<void>}
*/
/**
* @type BeforeNewThreadHookData[]
*/
const beforeNewThreadHooks = [];
/**
* @param {BeforeNewThreadHookData} fn
*/
function beforeNewThread(fn) {
beforeNewThreadHooks.push(fn);
}
/**
* @param {{
* user: Eris.User,
* opts: CreateNewThreadForUserOpts,
* }} input
* @return {Promise<BeforeNewThreadHookResult>}
*/
async function callBeforeNewThreadHooks(input) {
/**
* @type {BeforeNewThreadHookResult}
*/
const result = {
cancelled: false,
categoryId: null,
};
/**
* @type {BeforeNewThreadHookData}
*/
const data = {
...input,
cancel() {
result.cancelled = true;
},
setCategoryId(value) {
result.categoryId = value;
},
};
for (const hook of beforeNewThreadHooks) {
await hook(data);
}
return result;
}
module.exports = {
beforeNewThread,
callBeforeNewThreadHooks,
};

View File

@ -8,7 +8,7 @@ const {messageQueue} = require('./queue');
const utils = require('./utils'); const utils = require('./utils');
const { createCommandManager } = require('./commands'); const { createCommandManager } = require('./commands');
const { getPluginAPI, loadPlugin } = require('./plugins'); const { getPluginAPI, loadPlugin } = require('./plugins');
const { beforeNewThreadHooks } = require('./hooks'); const { callBeforeNewThreadHooks } = require('./hooks/beforeNewThread');
const blocked = require('./data/blocked'); const blocked = require('./data/blocked');
const threads = require('./data/threads'); const threads = require('./data/threads');
@ -132,28 +132,9 @@ function initBaseMessageHandlers() {
// Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc. // Ignore messages that shouldn't usually open new threads, such as "ok", "thanks", etc.
if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return; if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return;
let cancelled = false; thread = await threads.createNewThreadForUser(msg.author, {
let categoryId = null; source: 'dm',
});
/**
* @type {BeforeNewThreadHookEvent}
*/
const ev = {
cancel() {
cancelled = true;
},
setCategoryId(_categoryId) {
categoryId = _categoryId;
},
};
for (const hook of beforeNewThreadHooks) {
await hook(ev, msg);
if (cancelled) return;
}
thread = await threads.createNewThreadForUser(msg.author, { categoryId });
} }
if (thread) { if (thread) {

View File

@ -15,7 +15,12 @@ module.exports = ({ bot, knex, config, commands }) => {
return; return;
} }
const createdThread = await threads.createNewThreadForUser(user, { quiet: true, ignoreRequirements: true }); const createdThread = await threads.createNewThreadForUser(user, {
quiet: true,
ignoreRequirements: true,
source: 'command',
});
createdThread.postSystemMessage(`Thread was opened by ${msg.author.username}#${msg.author.discriminator}`); createdThread.postSystemMessage(`Thread was opened by ${msg.author.username}#${msg.author.discriminator}`);
msg.channel.createMessage(`Thread opened: <#${createdThread.channel_id}>`); msg.channel.createMessage(`Thread opened: <#${createdThread.channel_id}>`);

View File

@ -1,5 +1,5 @@
const attachments = require('./data/attachments'); const attachments = require('./data/attachments');
const { beforeNewThread } = require('./hooks'); const { beforeNewThread } = require('./hooks/beforeNewThread');
const formats = require('./formatters'); const formats = require('./formatters');
module.exports = { module.exports = {