Add support for timed blocks. Add !is_blocked.
parent
4bfd247be3
commit
1259669c9d
|
@ -35,10 +35,11 @@ See [CHANGELOG.md](CHANGELOG.md)
|
||||||
`!logs <user>` Lists previous modmail logs with the specified user
|
`!logs <user>` Lists previous modmail logs with the specified user
|
||||||
`!block <user>` Blocks the specified user from using modmail
|
`!block <user>` Blocks the specified user from using modmail
|
||||||
`!unblock <user>` Unblocks the specified user from using modmail
|
`!unblock <user>` Unblocks the specified user from using modmail
|
||||||
|
`!is_blocked <user>` Checks whether the user is blocked and for how long
|
||||||
`!s <shortcut> <text>` Adds a snippet (a canned response). See below for how to use it.
|
`!s <shortcut> <text>` Adds a snippet (a canned response). See below for how to use it.
|
||||||
`!edit_snippet <shortcut> <text>` Edits an existing snippet (alias `!es`)
|
`!edit_snippet <shortcut> <text>` Edits an existing snippet (alias `!es`)
|
||||||
`!delete_snippet <shortcut>` Deletes the specified snippet (alias `!ds`)
|
`!delete_snippet <shortcut>` Deletes the specified snippet (alias `!ds`)
|
||||||
`!snippets` Lists all available snippets
|
`!snippets` Lists all available snippets
|
||||||
`!version` Print the version of the bot you're running
|
`!version` Print the version of the bot you're running
|
||||||
`!newthread <user>` Opens a new thread with the specified user
|
`!newthread <user>` Opens a new thread with the specified user
|
||||||
|
|
||||||
|
@ -47,8 +48,8 @@ See [CHANGELOG.md](CHANGELOG.md)
|
||||||
`!anonreply <text>` Sends an anonymous reply to the user in the format "Role: text" (alias `!ar`)
|
`!anonreply <text>` Sends an anonymous reply to the user in the format "Role: text" (alias `!ar`)
|
||||||
`!close <time>` Closes the modmail thread. If a time is specified, the thread is scheduled to be closed later. Scheduled closing is cancelled if a message is sent to or received from the user.
|
`!close <time>` Closes the modmail thread. If a time is specified, the thread is scheduled to be closed later. Scheduled closing is cancelled if a message is sent to or received from the user.
|
||||||
`!logs` Lists previous modmail logs with this user
|
`!logs` Lists previous modmail logs with this user
|
||||||
`!block` Blocks the user from using modmail
|
`!block <time>` Blocks the user from using modmail. If a time is specified, the block is temporary.
|
||||||
`!unblock` Unblocks the user from using modmail
|
`!unblock <time>` Unblocks the user from using modmail. If a time is specified, the user will be scheduled to be unblocked after that time.
|
||||||
`!!shortcut` Reply with a snippet. Replace `shortcut` with the snippet's actual shortcut.
|
`!!shortcut` Reply with a snippet. Replace `shortcut` with the snippet's actual shortcut.
|
||||||
`!!!shortcut` Reply with a snippet anonymously. Replace `shortcut` with the snippet's actual shortcut.
|
`!!!shortcut` Reply with a snippet anonymously. Replace `shortcut` with the snippet's actual shortcut.
|
||||||
`!move <category>` If `allowMove` is enabled, moves the thread channel to the specified category
|
`!move <category>` If `allowMove` is enabled, moves the thread channel to the specified category
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
exports.up = async function(knex, Promise) {
|
||||||
|
await knex.schema.table('blocked_users', table => {
|
||||||
|
table.dateTime('expires_at').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = async function(knex, Promise) {
|
||||||
|
await knex.schema.table('blocked_users', table => {
|
||||||
|
table.dropColumn('expires_at');
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,17 +1,28 @@
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const knex = require('../knex');
|
const knex = require('../knex');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} userId
|
||||||
|
* @returns {Promise<{ isBlocked: boolean, expiresAt: string }>}
|
||||||
|
*/
|
||||||
|
async function getBlockStatus(userId) {
|
||||||
|
const row = await knex('blocked_users')
|
||||||
|
.where('user_id', userId)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isBlocked: !! row,
|
||||||
|
expiresAt: row && row.expires_at
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether userId is blocked
|
* Checks whether userId is blocked
|
||||||
* @param {String} userId
|
* @param {String} userId
|
||||||
* @returns {Promise<Boolean>}
|
* @returns {Promise<Boolean>}
|
||||||
*/
|
*/
|
||||||
async function isBlocked(userId) {
|
async function isBlocked(userId) {
|
||||||
const row = await knex('blocked_users')
|
return (await getBlockStatus(userId)).isBlocked;
|
||||||
.where('user_id', userId)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
return !! row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,7 +32,7 @@ async function isBlocked(userId) {
|
||||||
* @param {String} blockedBy
|
* @param {String} blockedBy
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async function block(userId, userName = '', blockedBy = null) {
|
async function block(userId, userName = '', blockedBy = null, expiresAt = null) {
|
||||||
if (await isBlocked(userId)) return;
|
if (await isBlocked(userId)) return;
|
||||||
|
|
||||||
return knex('blocked_users')
|
return knex('blocked_users')
|
||||||
|
@ -29,7 +40,8 @@ async function block(userId, userName = '', blockedBy = null) {
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
user_name: userName,
|
user_name: userName,
|
||||||
blocked_by: blockedBy,
|
blocked_by: blockedBy,
|
||||||
blocked_at: moment.utc().format('YYYY-MM-DD HH:mm:ss')
|
blocked_at: moment.utc().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
expires_at: expiresAt
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +56,39 @@ async function unblock(userId) {
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the expiry time of the block for the given userId
|
||||||
|
* @param {String} userId
|
||||||
|
* @param {String} expiresAt
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function updateExpiryTime(userId, expiresAt) {
|
||||||
|
return knex('blocked_users')
|
||||||
|
.where('user_id', userId)
|
||||||
|
.update({
|
||||||
|
expires_at: expiresAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {String[]}
|
||||||
|
*/
|
||||||
|
async function getExpiredBlocks() {
|
||||||
|
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
const blocks = await knex('blocked_users')
|
||||||
|
.whereNotNull('expires_at')
|
||||||
|
.where('expires_at', '<=', now)
|
||||||
|
.select();
|
||||||
|
|
||||||
|
return blocks.map(block => block.user_id);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
getBlockStatus,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
block,
|
block,
|
||||||
unblock,
|
unblock,
|
||||||
|
updateExpiryTime,
|
||||||
|
getExpiredBlocks,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
const humanizeDuration = require('humanize-duration');
|
||||||
|
const moment = require('moment');
|
||||||
const threadUtils = require('../threadUtils');
|
const threadUtils = require('../threadUtils');
|
||||||
const blocked = require("../data/blocked");
|
const blocked = require("../data/blocked");
|
||||||
const utils = require("../utils");
|
const utils = require("../utils");
|
||||||
|
@ -5,38 +7,95 @@ const utils = require("../utils");
|
||||||
module.exports = bot => {
|
module.exports = bot => {
|
||||||
const addInboxServerCommand = (...args) => threadUtils.addInboxServerCommand(bot, ...args);
|
const addInboxServerCommand = (...args) => threadUtils.addInboxServerCommand(bot, ...args);
|
||||||
|
|
||||||
addInboxServerCommand('block', (msg, args, thread) => {
|
async function removeExpiredBlocks() {
|
||||||
async function block(userId) {
|
const expiredBlocks = await blocked.getExpiredBlocks();
|
||||||
const user = bot.users.get(userId);
|
const logChannel = utils.getLogChannel();
|
||||||
await blocked.block(userId, (user ? `${user.username}#${user.discriminator}` : ''), msg.author.id);
|
for (const userId of expiredBlocks) {
|
||||||
msg.channel.createMessage(`Blocked <@${userId}> (id ${userId}) from modmail`);
|
await blocked.unblock(userId);
|
||||||
|
logChannel.createMessage(`Block of <@!${userId}> (id \`${userId}\`) expired`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expiredBlockLoop() {
|
||||||
|
try {
|
||||||
|
removeExpiredBlocks();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length > 0) {
|
setTimeout(expiredBlockLoop, 2000);
|
||||||
// User mention/id as argument
|
}
|
||||||
const userId = utils.getUserMention(args.join(' '));
|
|
||||||
if (! userId) return;
|
bot.on('ready', expiredBlockLoop);
|
||||||
block(userId);
|
|
||||||
} else if (thread) {
|
addInboxServerCommand('block', async (msg, args, thread) => {
|
||||||
// Calling !block without args in a modmail thread blocks the user of that thread
|
const userIdToBlock = args[0]
|
||||||
block(thread.user_id);
|
? utils.getUserMention(args[0])
|
||||||
|
: thread && thread.user_id;
|
||||||
|
|
||||||
|
if (! userIdToBlock) return;
|
||||||
|
|
||||||
|
const isBlocked = await blocked.isBlocked(userIdToBlock);
|
||||||
|
if (isBlocked) {
|
||||||
|
msg.channel.createMessage('User is already blocked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiryTime = args[1] ? utils.convertDelayStringToMS(args[1]) : null;
|
||||||
|
const expiresAt = expiryTime
|
||||||
|
? moment.utc().add(expiryTime, 'ms').format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const user = bot.users.get(userIdToBlock);
|
||||||
|
await blocked.block(userIdToBlock, (user ? `${user.username}#${user.discriminator}` : ''), msg.author.id, expiresAt);
|
||||||
|
|
||||||
|
if (expiresAt) {
|
||||||
|
const humanized = humanizeDuration(expiryTime, { largest: 2, round: true });
|
||||||
|
msg.channel.createMessage(`Blocked <@${userIdToBlock}> (id \`${userIdToBlock}\`) from modmail for ${humanized}`);
|
||||||
|
} else {
|
||||||
|
msg.channel.createMessage(`Blocked <@${userIdToBlock}> (id \`${userIdToBlock}\`) from modmail indefinitely`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addInboxServerCommand('unblock', (msg, args, thread) => {
|
addInboxServerCommand('unblock', async (msg, args, thread) => {
|
||||||
async function unblock(userId) {
|
const userIdToUnblock = args[0]
|
||||||
await blocked.unblock(userId);
|
? utils.getUserMention(args[0])
|
||||||
msg.channel.createMessage(`Unblocked <@${userId}> (id ${userId}) from modmail`);
|
: thread && thread.user_id;
|
||||||
}
|
|
||||||
|
|
||||||
if (args.length > 0) {
|
if (! userIdToUnblock) return;
|
||||||
// User mention/id as argument
|
|
||||||
const userId = utils.getUserMention(args.join(' '));
|
const unblockDelay = args[1] ? utils.convertDelayStringToMS(args[1]) : null;
|
||||||
if (! userId) return;
|
const unblockAt = unblockDelay
|
||||||
unblock(userId);
|
? moment.utc().add(unblockDelay, 'ms').format('YYYY-MM-DD HH:mm:ss')
|
||||||
} else if (thread) {
|
: null;
|
||||||
// Calling !unblock without args in a modmail thread unblocks the user of that thread
|
|
||||||
unblock(thread.user_id);
|
const user = bot.users.get(userIdToUnblock);
|
||||||
|
if (unblockAt) {
|
||||||
|
const humanized = humanizeDuration(unblockDelay, { largest: 2, round: true });
|
||||||
|
await blocked.updateExpiryTime(userIdToUnblock, unblockAt);
|
||||||
|
msg.channel.createMessage(`Scheduled <@${userIdToUnblock}> (id \`${userIdToUnblock}\`) to be unblocked in ${humanized}`);
|
||||||
|
} else {
|
||||||
|
await blocked.unblock(userIdToUnblock);
|
||||||
|
msg.channel.createMessage(`Unblocked <@${userIdToUnblock}> (id ${userIdToUnblock}) from modmail`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addInboxServerCommand('is_blocked', async (msg, args, thread) => {
|
||||||
|
const userIdToCheck = args[0]
|
||||||
|
? utils.getUserMention(args[0])
|
||||||
|
: thread && thread.user_id;
|
||||||
|
|
||||||
|
if (! userIdToCheck) return;
|
||||||
|
|
||||||
|
const blockStatus = await blocked.getBlockStatus(userIdToCheck);
|
||||||
|
if (blockStatus.isBlocked) {
|
||||||
|
if (blockStatus.expiresAt) {
|
||||||
|
msg.channel.createMessage(`<@!${userIdToCheck}> (id \`${userIdToCheck}\`) is blocked until ${blockStatus.expiresAt} (UTC)`);
|
||||||
|
} else {
|
||||||
|
msg.channel.createMessage(`<@!${userIdToCheck}> (id \`${userIdToCheck}\`) is blocked indefinitely`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg.channel.createMessage(`<@!${userIdToCheck}> (id \`${userIdToCheck}\`) is NOT blocked`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -224,7 +224,7 @@ const delayStringRegex = /^([0-9]+)(?:([dhms])[a-z]*)?/i;
|
||||||
/**
|
/**
|
||||||
* Turns a "delay string" such as "1h30m" to milliseconds
|
* Turns a "delay string" such as "1h30m" to milliseconds
|
||||||
* @param {String} str
|
* @param {String} str
|
||||||
* @returns {Number}
|
* @returns {Number|null}
|
||||||
*/
|
*/
|
||||||
function convertDelayStringToMS(str) {
|
function convertDelayStringToMS(str) {
|
||||||
let match;
|
let match;
|
||||||
|
|
Loading…
Reference in New Issue