Allow setting a default display role

Same command as for threads, !role, but used outside a thread.
cshd
Dragory 2020-10-22 00:17:14 +03:00
parent 405a19b918
commit bbca6a873f
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
10 changed files with 298 additions and 92 deletions

View File

@ -12,11 +12,18 @@ Please report any bugs you encounter by [creating a GitHub issue](https://github
* The symbols used can be changed with the `inlineSnippetStart` and `inlineSnippetEnd` options * The symbols used can be changed with the `inlineSnippetStart` and `inlineSnippetEnd` options
* This feature can be disabled by setting `allowInlineSnippets = off` in your config * This feature can be disabled by setting `allowInlineSnippets = off` in your config
* By default, the bot will refuse to send a reply with an unknown inline snippet. To disable this behavior, set `errorOnUnknownInlineSnippet = off`. * By default, the bot will refuse to send a reply with an unknown inline snippet. To disable this behavior, set `errorOnUnknownInlineSnippet = off`.
* Moderators can now set the role they'd like to be displayed with their replies on a per-thread basis by using `!role` * Moderators can now set the role they'd like to be displayed with their replies ("display role") by default and on a per-thread basis by using `!role`
* Moderators can only choose roles they currently have * Moderators can only choose roles they currently have
* You can view your currently displayed role by using `!role` * You can view your current display role by using `!role`
* You can set the displayed role by using `!role <role name>`, e.g. `!role Interviewer` * If you're in a modmail thread, this will show your display role for that thread
* This feature can be disabled by setting `allowChangingDisplayedRole = off` * If you're *not* in a modmail thread, this will show your *default* display role
* You can set the display role by using `!role <role name>`, e.g. `!role Interviewer`
* If you're in a modmail thread, this will set your display role for that thread
* If you're *not* in a modmail thread, this will set your *default* display role
* You can reset the display role by using `!role reset`
* If you're in a modmail thread, this will reset your display role for that thread to the default
* If you're *not* in a modmail thread, this will reset your *default* display role
* This feature can be disabled by setting `allowChangingDisplayRole = off`
* New option: `fallbackRoleName` * New option: `fallbackRoleName`
* Sets the role name to display in moderator replies if the moderator doesn't have a hoisted role * Sets the role name to display in moderator replies if the moderator doesn't have a hoisted role
* Unless `fallbackRoleName` is set, anonymous replies without a role will no longer display "Moderator:" at the beginning of the message * Unless `fallbackRoleName` is set, anonymous replies without a role will no longer display "Moderator:" at the beginning of the message

View File

@ -71,10 +71,13 @@ Delete your own previous reply sent with `!reply`.
`<number>` is the message number shown in front of staff replies in the thread channel. `<number>` is the message number shown in front of staff replies in the thread channel.
### `!role` ### `!role`
View the role that is sent with your replies View your display role for the thread - the role that is shown in front of your name in your replies
### `!role`
Reset your display role for the thread to the default
### `!role <role name>` ### `!role <role name>`
Change the role that is sent with your replies to any role you currently have Change your display role for the thread to any role you currently have
### `!loglink` ### `!loglink`
Get a link to the open Modmail thread's log. Get a link to the open Modmail thread's log.
@ -129,6 +132,15 @@ Check if the specified user is blocked.
**Example:** `!is_blocked 106391128718245888` **Example:** `!is_blocked 106391128718245888`
### `!role`
(Outside a modmail thread) View your default display role - the role that is shown in front of your name in your replies
### `!role`
(Outside a modmail thread) Reset your default display role
### `!role <role name>`
(Outside a modmail thread) Change your default display role to any role you currently have
### `!version` ### `!version`
Show the Modmail bot's version. Show the Modmail bot's version.

View File

@ -102,7 +102,7 @@ If enabled, staff members can edit their own replies in modmail threads with `!e
If enabled, snippets can be included *within* replies by wrapping the snippet's name in {{ and }}. If enabled, snippets can be included *within* replies by wrapping the snippet's name in {{ and }}.
E.g. `!r Hello! {{rules}}` E.g. `!r Hello! {{rules}}`
#### allowChangingDisplayedRole #### allowChangingDisplayRole
**Default:** `on` **Default:** `on`
If enabled, moderators can change the role that's shown with their replies to any role they currently have using the `!role` command. If enabled, moderators can change the role that's shown with their replies to any role they currently have using the `!role` command.

View File

@ -11,6 +11,13 @@ const Thread = require("./data/Thread");
* @param {object} args * @param {object} args
*/ */
/**
* @callback InboxServerCommandHandler
* @param {Eris.Message} msg
* @param {object} args
* @param {Thread} [thread]
*/
/** /**
* @callback InboxThreadCommandHandler * @callback InboxThreadCommandHandler
* @param {Eris.Message} msg * @param {Eris.Message} msg
@ -30,7 +37,7 @@ const Thread = require("./data/Thread");
* @callback AddInboxServerCommandFn * @callback AddInboxServerCommandFn
* @param {string} trigger * @param {string} trigger
* @param {string} parameters * @param {string} parameters
* @param {CommandFn} handler * @param {InboxServerCommandHandler} handler
* @param {ICommandConfig} commandConfig * @param {ICommandConfig} commandConfig
*/ */

View File

@ -9,13 +9,12 @@ const attachments = require("./attachments");
const { formatters } = require("../formatters"); const { formatters } = require("../formatters");
const { callAfterThreadCloseHooks } = require("../hooks/afterThreadClose"); const { callAfterThreadCloseHooks } = require("../hooks/afterThreadClose");
const snippets = require("./snippets"); const snippets = require("./snippets");
const { getModeratorThreadDisplayRoleName } = require("./moderatorRoleOverrides");
const ThreadMessage = require("./ThreadMessage"); const ThreadMessage = require("./ThreadMessage");
const {THREAD_MESSAGE_TYPE, THREAD_STATUS, DISCORD_MESSAGE_ACTIVITY_TYPES} = require("./constants"); const {THREAD_MESSAGE_TYPE, THREAD_STATUS, DISCORD_MESSAGE_ACTIVITY_TYPES} = require("./constants");
const ROLE_OVERRIDES_METADATA_KEY = "moderatorRoleOverrides";
/** /**
* @property {String} id * @property {String} id
* @property {Number} status * @property {Number} status
@ -195,7 +194,7 @@ class Thread {
*/ */
async replyToUser(moderator, text, replyAttachments = [], isAnonymous = false) { async replyToUser(moderator, text, replyAttachments = [], isAnonymous = false) {
const moderatorName = config.useNicknames && moderator.nick ? moderator.nick : moderator.user.username; const moderatorName = config.useNicknames && moderator.nick ? moderator.nick : moderator.user.username;
const roleName = this.getModeratorDisplayRoleName(moderator); const roleName = await getModeratorThreadDisplayRoleName(moderator, this.id);
if (config.allowInlineSnippets) { if (config.allowInlineSnippets) {
// Replace {{snippet}} with the corresponding snippet // Replace {{snippet}} with the corresponding snippet
@ -838,43 +837,6 @@ class Thread {
}); });
} }
setModeratorRoleOverride(moderatorId, roleId) {
const moderatorRoleOverrides = this.getMetadataValue(ROLE_OVERRIDES_METADATA_KEY) || {};
moderatorRoleOverrides[moderatorId] = roleId;
return this.setMetadataValue(ROLE_OVERRIDES_METADATA_KEY, moderatorRoleOverrides);
}
resetModeratorRoleOverride(moderatorId) {
const moderatorRoleOverrides = this.getMetadataValue(ROLE_OVERRIDES_METADATA_KEY) || {};
delete moderatorRoleOverrides[moderatorId];
return this.setMetadataValue(ROLE_OVERRIDES_METADATA_KEY, moderatorRoleOverrides)
}
/**
* Get the role that is shown in the replies of the specified moderator,
* taking role overrides into account.
* @param {Eris.Member} moderator
* @return {Eris.Role|undefined}
*/
getModeratorDisplayRole(moderator) {
const moderatorRoleOverrides = this.getMetadataValue(ROLE_OVERRIDES_METADATA_KEY) || {};
const overrideRoleId = moderatorRoleOverrides[moderator.id];
const overrideRole = overrideRoleId && moderator.roles.includes(overrideRoleId) && utils.getInboxGuild().roles.get(overrideRoleId);
const finalRole = overrideRole || utils.getMainRole(moderator);
return finalRole;
}
/**
* Get the role NAME that is shown in the replies of the specified moderator,
* taking role overrides into account.
* @param {Eris.Member} moderator
* @return {Eris.Role|undefined}
*/
getModeratorDisplayRoleName(moderator) {
const displayRole = this.getModeratorDisplayRole(moderator);
return displayRole ? displayRole.name : null;
}
/** /**
* @param {string} key * @param {string} key
* @param {*} value * @param {*} value

View File

@ -60,7 +60,7 @@
* @property {string} [inlineSnippetStart="{{"] * @property {string} [inlineSnippetStart="{{"]
* @property {string} [inlineSnippetEnd="}}"] * @property {string} [inlineSnippetEnd="}}"]
* @property {boolean} [errorOnUnknownInlineSnippet=true] * @property {boolean} [errorOnUnknownInlineSnippet=true]
* @property {boolean} [allowChangingDisplayedRole=true] * @property {boolean} [allowChangingDisplayRole=true]
* @property {string} [fallbackRoleName=null] * @property {string} [fallbackRoleName=null]
* @property {string} [logStorage="local"] * @property {string} [logStorage="local"]
* @property {object} [logOptions] * @property {object} [logOptions]

View File

@ -336,7 +336,7 @@
"default": true "default": true
}, },
"allowChangingDisplayedRole": { "allowChangingDisplayRole": {
"$ref": "#/definitions/customBoolean", "$ref": "#/definitions/customBoolean",
"default": true "default": true
}, },

View File

@ -0,0 +1,17 @@
exports.up = async function(knex, Promise) {
if (! await knex.schema.hasTable("moderator_role_overrides")) {
await knex.schema.createTable("moderator_role_overrides", table => {
table.string("moderator_id", 20);
table.string("thread_id", 36).nullable().defaultTo(null);
table.string("role_id", 20);
table.primary(["moderator_id", "thread_id"]);
});
}
};
exports.down = async function(knex, Promise) {
if (await knex.schema.hasTable("moderator_role_overrides")) {
await knex.schema.dropTable("moderator_role_overrides");
}
};

View File

@ -0,0 +1,165 @@
const knex = require("../knex");
const Eris = require("eris");
const utils = require("../utils");
const config = require("../cfg");
/**
* @param {string} moderatorId
* @returns {Promise<string|null>}
*/
async function getModeratorDefaultRoleOverride(moderatorId) {
const roleOverride = await knex("moderator_role_overrides")
.where("moderator_id", moderatorId)
.whereNull("thread_id")
.first();
return roleOverride ? roleOverride.role_id : null;
}
/**
* @param {string} moderatorId
* @param {string} roleId
* @returns {Promise<void>}
*/
async function setModeratorDefaultRoleOverride(moderatorId, roleId) {
const existingGlobalOverride = await getModeratorDefaultRoleOverride(moderatorId);
if (existingGlobalOverride) {
await knex("moderator_role_overrides")
.where("moderator_id", moderatorId)
.whereNull("thread_id")
.update({ role_id: roleId });
} else {
await knex("moderator_role_overrides")
.insert({
moderator_id: moderatorId,
thread_id: null,
role_id: roleId,
});
}
}
/**
* @param {string} moderatorId
* @returns {Promise<void>}
*/
async function resetModeratorDefaultRoleOverride(moderatorId) {
await knex("moderator_role_overrides")
.where("moderator_id", moderatorId)
.whereNull("thread_id")
.delete();
}
/**
* @param {string} moderatorId
* @param {string} threadId
* @returns {Promise<string|null>}
*/
async function getModeratorThreadRoleOverride(moderatorId, threadId) {
const roleOverride = await knex("moderator_role_overrides")
.where("moderator_id", moderatorId)
.where("thread_id", threadId)
.first();
return roleOverride ? roleOverride.role_id : null;
}
/**
* @param {string} moderatorId
* @param {string} threadId
* @param {string} roleId
* @returns {Promise<void>}
*/
async function setModeratorThreadRoleOverride(moderatorId, threadId, roleId) {
const existingGlobalOverride = await getModeratorThreadRoleOverride(moderatorId, threadId);
if (existingGlobalOverride) {
await knex("moderator_role_overrides")
.where("moderator_id", moderatorId)
.where("thread_id", threadId)
.update({ role_id: roleId });
} else {
await knex("moderator_role_overrides")
.insert({
moderator_id: moderatorId,
thread_id: threadId,
role_id: roleId,
});
}
}
/**
* @param {string} moderatorId
* @param {string} threadId
* @returns {Promise<void>}
*/
async function resetModeratorThreadRoleOverride(moderatorId, threadId) {
await knex("moderator_role_overrides")
.where("moderator_id", moderatorId)
.where("thread_id", threadId)
.delete();
}
/**
* @param {Eris.Member} moderator
* @returns {Promise<Eris.Role|null>}
*/
async function getModeratorDefaultDisplayRole(moderator) {
const globalOverrideRoleId = await getModeratorDefaultRoleOverride(moderator.id);
if (globalOverrideRoleId && moderator.roles.includes(globalOverrideRoleId)) {
return moderator.guild.roles.get(globalOverrideRoleId);
}
return utils.getMainRole(moderator);
}
/**
* @param {Eris.Member} moderator
* @returns {Promise<string|null>}
*/
async function getModeratorDefaultDisplayRoleName(moderator) {
const defaultDisplayRole = await getModeratorDefaultDisplayRole(moderator);
return defaultDisplayRole
? defaultDisplayRole.name
: (config.fallbackRoleName || null);
}
/**
* @param {Eris.Member} moderator
* @param {string} threadId
* @returns {Promise<Eris.Role|null>}
*/
async function getModeratorThreadDisplayRole(moderator, threadId) {
const threadOverrideRoleId = await getModeratorThreadRoleOverride(moderator.id, threadId);
if (threadOverrideRoleId && moderator.roles.includes(threadOverrideRoleId)) {
return moderator.guild.roles.get(threadOverrideRoleId);
}
return getModeratorDefaultDisplayRole(moderator);
}
/**
* @param {Eris.Member} moderator
* @param {string} threadId
* @returns {Promise<string|null>}
*/
async function getModeratorThreadDisplayRoleName(moderator, threadId) {
const threadDisplayRole = await getModeratorThreadDisplayRole(moderator, threadId);
return threadDisplayRole
? threadDisplayRole.name
: (config.fallbackRoleName || null);
}
module.exports = {
getModeratorDefaultRoleOverride,
setModeratorDefaultRoleOverride,
resetModeratorDefaultRoleOverride,
getModeratorThreadRoleOverride,
setModeratorThreadRoleOverride,
resetModeratorThreadRoleOverride,
getModeratorDefaultDisplayRole,
getModeratorDefaultDisplayRoleName,
getModeratorThreadDisplayRole,
getModeratorThreadDisplayRoleName,
};

View File

@ -1,63 +1,99 @@
const utils = require("../utils"); const utils = require("../utils");
const {
setModeratorDefaultRoleOverride,
resetModeratorDefaultRoleOverride,
setModeratorThreadRoleOverride,
resetModeratorThreadRoleOverride,
getModeratorThreadDisplayRoleName,
getModeratorDefaultDisplayRoleName,
} = require("../data/moderatorRoleOverrides");
const ROLE_OVERRIDES_METADATA_KEY = "moderatorRoleOverrides"; const ROLE_OVERRIDES_METADATA_KEY = "moderatorRoleOverrides";
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
if (! config.allowChangingDisplayedRole) { if (! config.allowChangingDisplayRole) {
return; return;
} }
commands.addInboxThreadCommand("role", "[role:string$]", async (msg, args, thread) => { function resolveRoleInput(input) {
const moderatorRoleOverrides = thread.getMetadataValue(ROLE_OVERRIDES_METADATA_KEY); if (utils.isSnowflake(input)) {
return utils.getInboxGuild().roles.get(input);
}
// Set display role return utils.getInboxGuild().roles.find(r => r.name.toLowerCase() === input.toLowerCase());
if (args.role) { }
if (args.role === "reset") {
await thread.resetModeratorRoleOverride(msg.member.id);
const displayRole = thread.getModeratorDisplayRoleName(msg.member); // Get display role for a thread
if (displayRole) { commands.addInboxThreadCommand("role", [], async (msg, args, thread) => {
thread.postSystemMessage(`Your display role has been reset. Your replies will now display the role **${displayRole}**.`); const displayRole = await getModeratorThreadDisplayRoleName(msg.member, thread.id);
} else { if (displayRole) {
thread.postSystemMessage("Your display role has been reset. Your replies will no longer display a role."); thread.postSystemMessage(`Your display role in this thread is currently **${displayRole}**`);
} } else {
thread.postSystemMessage("Your replies in this thread do not currently display a role");
}
});
return; // Reset display role for a thread
} commands.addInboxThreadCommand("role reset", [], async (msg, args, thread) => {
await resetModeratorThreadRoleOverride(msg.member.id, thread.id);
let role; const displayRole = await getModeratorThreadDisplayRoleName(msg.member, thread.id);
if (utils.isSnowflake(args.role)) { if (displayRole) {
if (! msg.member.roles.includes(args.role)) { thread.postSystemMessage(`Your display role for this thread has been reset. Your replies will now display the default role **${displayRole}**.`);
thread.postSystemMessage("No matching role found. Make sure you have the role before trying to set it as your role."); } else {
return; thread.postSystemMessage("Your display role for this thread has been reset. Your replies will no longer display a role.");
} }
}, {
aliases: ["role_reset", "reset_role"],
});
role = utils.getInboxGuild().roles.get(args.role); // Set display role for a thread
} else { commands.addInboxThreadCommand("role", "<role:string$>", async (msg, args, thread) => {
const matchingMemberRole = utils.getInboxGuild().roles.find(r => { const role = resolveRoleInput(args.role);
if (! msg.member.roles.includes(r.id)) return false; if (! role || ! msg.member.roles.includes(role.id)) {
return r.name.toLowerCase() === args.role.toLowerCase(); thread.postSystemMessage("No matching role found. Make sure you have the role before trying to set it as your display role in this thread.");
});
if (! matchingMemberRole) {
thread.postSystemMessage("No matching role found. Make sure you have the role before trying to set it as your role.");
return;
}
role = matchingMemberRole;
}
await thread.setModeratorRoleOverride(msg.member.id, role.id);
thread.postSystemMessage(`Your display role has been set to **${role.name}**. You can reset it with \`${config.prefix}role reset\`.`);
return; return;
} }
// Get display role await setModeratorThreadRoleOverride(msg.member.id, thread.id, role.id);
const displayRole = thread.getModeratorDisplayRoleName(msg.member); thread.postSystemMessage(`Your display role for this thread has been set to **${role.name}**. You can reset it with \`${config.prefix}role reset\`.`);
});
// Get default display role
commands.addInboxServerCommand("role", [], async (msg, args, thread) => {
const displayRole = await getModeratorDefaultDisplayRoleName(msg.member);
if (displayRole) { if (displayRole) {
thread.postSystemMessage(`Your displayed role is currently: **${displayRole}**`); msg.channel.createMessage(`Your default display role is currently **${displayRole}**`);
} else { } else {
thread.postSystemMessage("Your replies do not currently display a role"); msg.channel.createMessage("Your replies do not currently display a role by default");
} }
}); });
// Reset default display role
commands.addInboxServerCommand("role reset", [], async (msg, args, thread) => {
await resetModeratorDefaultRoleOverride(msg.member.id);
const displayRole = await getModeratorDefaultDisplayRoleName(msg.member);
if (displayRole) {
msg.channel.createMessage(`Your default display role has been reset. Your replies will now display the role **${displayRole}** by default.`);
} else {
msg.channel.createMessage("Your default display role has been reset. Your replies will no longer display a role by default.");
}
}, {
aliases: ["role_reset", "reset_role"],
});
// Set default display role
commands.addInboxServerCommand("role", "<role:string$>", async (msg, args, thread) => {
const role = resolveRoleInput(args.role);
if (! role || ! msg.member.roles.includes(role.id)) {
msg.channel.createMessage("No matching role found. Make sure you have the role before trying to set it as your default display role.");
return;
}
await setModeratorDefaultRoleOverride(msg.member.id, role.id);
msg.channel.createMessage(`Your default display role has been set to **${role.name}**. You can reset it with \`${config.prefix}role reset\`.`);
});
}; };