canned replies, logs etc etc
This commit is contained in:
parent
05eb2ef0f1
commit
2a9218e7f3
@ -131,6 +131,21 @@ class ModmailClient extends Client {
|
|||||||
return this.resolver.resolveUser(input);
|
return this.resolver.resolveUser(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async prompt(str, { author, channel, time }) {
|
||||||
|
|
||||||
|
if (!channel && author) channel = await author.createDM();
|
||||||
|
if (!channel) throw new Error(`Missing channel for prompt, must pass at least author.`);
|
||||||
|
await channel.send(str);
|
||||||
|
return channel.awaitMessages((m) => m.author.id === author.id, { max: 1, time: time || 30000, errors: ['time'] })
|
||||||
|
.then((collected) => {
|
||||||
|
return collected.first();
|
||||||
|
})
|
||||||
|
.catch((error) => { //eslint-disable-line no-unused-vars, handle-callback-err
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ModmailClient;
|
module.exports = ModmailClient;
|
@ -34,6 +34,7 @@ class Modmail {
|
|||||||
|
|
||||||
// Sweep graveyard every 30 min and move stale channels to graveyard
|
// Sweep graveyard every 30 min and move stale channels to graveyard
|
||||||
this.sweeper = setInterval(this.sweepChannels.bind(this), 5 * 60 * 1000);
|
this.sweeper = setInterval(this.sweepChannels.bind(this), 5 * 60 * 1000);
|
||||||
|
this.saver = setInterval(this.saveHistory.bind(this), 30 * 1000);
|
||||||
|
|
||||||
let logStr = `Started modmail handler for ${this.mainServer.name}`;
|
let logStr = `Started modmail handler for ${this.mainServer.name}`;
|
||||||
if (this.bansServer) logStr += ` with ${this.bansServer.name} for ban appeals`;
|
if (this.bansServer) logStr += ` with ${this.bansServer.name} for ban appeals`;
|
||||||
@ -125,8 +126,7 @@ class Modmail {
|
|||||||
if (!cache._channels) cache._channels = {};
|
if (!cache._channels) cache._channels = {};
|
||||||
cache._channels[author.id] = channel;
|
cache._channels[author.id] = channel;
|
||||||
|
|
||||||
this.mmcache[author.id] = pastModmail;
|
pastModmail.push({ author: author.id, content, timestamp: Date.now(), isReply: false });
|
||||||
this.mmcache[author.id].push({ author: author.id, content, timestamp: Date.now(), isReply: false });
|
|
||||||
if (!this.updatedThreads.includes(author.id)) this.updatedThreads.push(author.id);
|
if (!this.updatedThreads.includes(author.id)) this.updatedThreads.push(author.id);
|
||||||
|
|
||||||
const embed = {
|
const embed = {
|
||||||
@ -154,8 +154,6 @@ class Modmail {
|
|||||||
this.client.logger.error(`channel.send errored:\n${err.stack}\nContent: "${content}"`);
|
this.client.logger.error(`channel.send errored:\n${err.stack}\nContent: "${content}"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!this.timeout || this.timeout._destroyed) this.timeout = setTimeout(this.saveHistory.bind(this), 30 * 1000);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,6 +215,10 @@ class Modmail {
|
|||||||
for (let i = context < len ? context : len; i > 0; i--) {
|
for (let i = context < len ? context : len; i > 0; i--) {
|
||||||
const entry = history[len - i];
|
const entry = history[len - i];
|
||||||
if (!entry) continue;
|
if (!entry) continue;
|
||||||
|
if (entry.markread) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const mem = entry.author.id === member.id ? member : this.mainServer.members.resolve(entry.author);
|
const mem = entry.author.id === member.id ? member : this.mainServer.members.resolve(entry.author);
|
||||||
|
|
||||||
@ -350,25 +352,24 @@ class Modmail {
|
|||||||
this.client.logger.error(`Error during channel transition:\n${err.stack}`);
|
this.client.logger.error(`Error during channel transition:\n${err.stack}`);
|
||||||
});
|
});
|
||||||
await message.delete().catch(this.client.logger.warn.bind(this.client.logger));
|
await message.delete().catch(this.client.logger.warn.bind(this.client.logger));
|
||||||
|
if (!this.updatedThreads.includes(author.id)) this.updatedThreads.push(author.id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendModmail({ message, content, anon, target }) {
|
async sendModmail({ message, content, anon, target }) {
|
||||||
|
|
||||||
console.log(content, anon, target.tag);
|
|
||||||
|
|
||||||
const targetMember = await this.getMember(target.id);
|
const targetMember = await this.getMember(target.id);
|
||||||
if (!targetMember) return {
|
if (!targetMember) return {
|
||||||
error: true,
|
error: true,
|
||||||
msg: `Cannot find member`
|
msg: `Cannot find member`
|
||||||
};
|
};
|
||||||
|
|
||||||
const pastModmail = await this.loadHistory(target.id)
|
const history = await this.loadHistory(target.id)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.client.logger.error(`Error during loading of past mail:\n${err.stack}`);
|
this.client.logger.error(`Error during loading of past mail:\n${err.stack}`);
|
||||||
return { error: true };
|
return { error: true };
|
||||||
});
|
});
|
||||||
if (pastModmail.error) return {
|
if (history.error) return {
|
||||||
error: true,
|
error: true,
|
||||||
msg: `Internal error, this has been logged.`
|
msg: `Internal error, this has been logged.`
|
||||||
};
|
};
|
||||||
@ -395,7 +396,9 @@ class Modmail {
|
|||||||
if (sent.error) return sent;
|
if (sent.error) return sent;
|
||||||
|
|
||||||
await message.channel.send('Delivered.').catch(this.client.logger.error.bind(this.client.logger));
|
await message.channel.send('Delivered.').catch(this.client.logger.error.bind(this.client.logger));
|
||||||
const channel = await this.loadChannel(targetMember, pastModmail).catch(this.client.logger.error.bind(this.client.logger));
|
const channel = await this.loadChannel(targetMember, history).catch(this.client.logger.error.bind(this.client.logger));
|
||||||
|
history.push({ author: member.id, content, timestamp: Date.now(), isReply: true, anon });
|
||||||
|
if (!this.updatedThreads.includes(author.id)) this.updatedThreads.push(author.id);
|
||||||
await channel.send({ embed }).catch(this.client.logger.error.bind(this.client.logger));
|
await channel.send({ embed }).catch(this.client.logger.error.bind(this.client.logger));
|
||||||
await channel.edit({ parentID: this.readMail.id }).catch(this.client.logger.error.bind(this.client.logger));
|
await channel.edit({ parentID: this.readMail.id }).catch(this.client.logger.error.bind(this.client.logger));
|
||||||
|
|
||||||
@ -465,14 +468,37 @@ class Modmail {
|
|||||||
|
|
||||||
async markread(message) {
|
async markread(message) {
|
||||||
|
|
||||||
const { channel } = message;
|
const { channel, author } = message;
|
||||||
|
|
||||||
if (!this.categories.includes(channel.parentID)) return {
|
if (!this.categories.includes(channel.parentID)) return {
|
||||||
error: true,
|
error: true,
|
||||||
msg: `This command only works in modmail channels.`
|
msg: `This command only works in modmail channels.`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const chCache = this.client.cache.channels;
|
||||||
|
const result = Object.entries(chCache).find(([, val]) => {
|
||||||
|
return val === channel.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return {
|
||||||
|
error: true,
|
||||||
|
msg: `This doesn't seem to be a valid modmail channel. Cache might be out of sync. **[MISSING TARGET]**`
|
||||||
|
};
|
||||||
|
|
||||||
|
const [userId] = result;
|
||||||
|
const history = await this.loadHistory(userId)
|
||||||
|
.catch((err) => {
|
||||||
|
this.client.logger.error(`Error during loading of past mail:\n${err.stack}`);
|
||||||
|
return { error: true };
|
||||||
|
});
|
||||||
|
if (history.error) return {
|
||||||
|
error: true,
|
||||||
|
msg: `Internal error, this has been logged.`
|
||||||
|
};
|
||||||
|
history.push({ author: author.id, timestamp: Date.now(), markread: true }); // To keep track of read state
|
||||||
|
|
||||||
await channel.edit({ parentID: this.readMail.id });
|
await channel.edit({ parentID: this.readMail.id });
|
||||||
|
if (!this.updatedThreads.includes(author.id)) this.updatedThreads.push(userId);
|
||||||
return `Done`;
|
return `Done`;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -488,7 +514,9 @@ class Modmail {
|
|||||||
|
|
||||||
fs.readFile(path, { encoding: 'utf-8' }, (err, data) => {
|
fs.readFile(path, { encoding: 'utf-8' }, (err, data) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
resolve(JSON.parse(data));
|
const parsed = JSON.parse(data);
|
||||||
|
this.mmcache[userId] = parsed;
|
||||||
|
resolve(parsed);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -515,11 +543,19 @@ class Modmail {
|
|||||||
|
|
||||||
loadReplies() {
|
loadReplies() {
|
||||||
|
|
||||||
|
this.client.logger.info('Loading canned replies');
|
||||||
if (!fs.existsSync('./canned_replies.json')) return {};
|
if (!fs.existsSync('./canned_replies.json')) return {};
|
||||||
return JSON.parse(fs.readFileSync('./canned_replies.json', { encoding: 'utf-8' }));
|
return JSON.parse(fs.readFileSync('./canned_replies.json', { encoding: 'utf-8' }));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveReplies() {
|
||||||
|
|
||||||
|
this.client.logger.info('Saving canned replies');
|
||||||
|
fs.writeFileSync('./canned_replies.json', JSON.stringify(this.replies));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Modmail;
|
module.exports = Modmail;
|
@ -15,17 +15,69 @@ class CannedReply extends Command {
|
|||||||
|
|
||||||
const [first] = args.map((a) => a);
|
const [first] = args.map((a) => a);
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let { content, _caller } = message,
|
let { channel, content, _caller } = message,
|
||||||
anon = false;
|
anon = false;
|
||||||
content = content.replace(`${this.client.prefix}${_caller}`, '');
|
content = content.replace(`${this.client.prefix}${_caller}`, '');
|
||||||
if (first.toLowerCase() === 'anon') {
|
const op = args.shift().toLowerCase();
|
||||||
|
if (op === 'anon') {
|
||||||
anon = true;
|
anon = true;
|
||||||
content = content.replace(first, '');
|
content = content.replace(first, '');
|
||||||
|
} else if (['create', 'delete'].includes(op)) {
|
||||||
|
return this.createCanned(op, args, message);
|
||||||
|
} else if (['list'].includes(first.toLowerCase(op))) {
|
||||||
|
|
||||||
|
const list = Object.entries(this.client.modmail.replies);
|
||||||
|
let str = '';
|
||||||
|
for (const [name, content] of list) {
|
||||||
|
if (str.length + content.length > 2000) {
|
||||||
|
await channel.send(str).catch(this.client.logger.error.bind(this.client.logger));
|
||||||
|
str = '';
|
||||||
|
}
|
||||||
|
str += `**${name}:** ${content}\n`;
|
||||||
|
}
|
||||||
|
if (str.length) await channel.send(str).catch(this.client.logger.error.bind(this.client.logger));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return this.client.modmail.sendCannedResponse({ message, responseName: content.trim(), anon });
|
return this.client.modmail.sendCannedResponse({ message, responseName: content.trim(), anon });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createCanned(op, args, { channel, author }) {
|
||||||
|
|
||||||
|
if (args.length < 1) return {
|
||||||
|
error: true,
|
||||||
|
msg: 'Missing reply name'
|
||||||
|
};
|
||||||
|
const [_name, ...rest] = args;
|
||||||
|
|
||||||
|
const name = _name.toLowerCase();
|
||||||
|
const canned = this.client.modmail.replies;
|
||||||
|
let confirmation = null;
|
||||||
|
|
||||||
|
if (op === 'create') {
|
||||||
|
if (!rest.length) return {
|
||||||
|
error: true,
|
||||||
|
msg: 'Missing content'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (canned[name]) {
|
||||||
|
confirmation = await this.client.prompt(`A canned reply by the name ${name} already exists, would you like to overwrite it?`, { channel, author });
|
||||||
|
if (!confirmation) return 'Timed out.';
|
||||||
|
confirmation = ['y', 'yes', 'ok'].includes(confirmation.content.toLowerCase());
|
||||||
|
if (!confirmation) return 'Cancelled';
|
||||||
|
}
|
||||||
|
|
||||||
|
canned[name] = rest.join(' ');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
delete canned[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.modmail.saveReplies();
|
||||||
|
return `Updated ${_name}`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = CannedReply;
|
module.exports = CannedReply;
|
71
structure/commands/Logs.js
Normal file
71
structure/commands/Logs.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const Command = require('../Command');
|
||||||
|
|
||||||
|
class Logs extends Command {
|
||||||
|
|
||||||
|
constructor(client) {
|
||||||
|
super(client, {
|
||||||
|
name: 'logs',
|
||||||
|
aliases: ['mmlogs', 'mmhistory'],
|
||||||
|
showUsage: true,
|
||||||
|
usage: '<user> [page]'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(message, args) {
|
||||||
|
|
||||||
|
const user = await this.client.resolveUser(args[0]);
|
||||||
|
let pageNr = 1;
|
||||||
|
if (args[1]) {
|
||||||
|
const num = parseInt(args[1]);
|
||||||
|
if (isNaN(num)) return {
|
||||||
|
error: true,
|
||||||
|
msg: 'Invalid page number, must be number'
|
||||||
|
};
|
||||||
|
pageNr = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { member, channel } = message;
|
||||||
|
const history = await this.client.modmail.loadHistory(user.id);
|
||||||
|
const page = this.paginate([...history].reverse(), pageNr, 10);
|
||||||
|
|
||||||
|
const embed = {
|
||||||
|
author: {
|
||||||
|
name: `${user.tag} modmail history`,
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
icon_url: user.displayAvatarURL({ dynamic: true })
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
text: `${user.id} | Page ${page.page}/${page.maxPage}`
|
||||||
|
},
|
||||||
|
fields: [],
|
||||||
|
color: member.highestRoleColor
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const entry of page.items) {
|
||||||
|
const user = await this.client.resolveUser(entry.author);
|
||||||
|
embed.fields.push({
|
||||||
|
name: `${user.tag}${entry.anon ? ' (ANON)' : ''} @ ${new Date(entry.timestamp).toUTCString()}`,
|
||||||
|
value: entry.content.substring(0, 1000) + (entry.content.length > 1000 ? '...' : '')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await channel.send({ embed });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
paginate(items, page = 1, pageLength = 10) {
|
||||||
|
const maxPage = Math.ceil(items.length / pageLength);
|
||||||
|
if (page < 1) page = 1;
|
||||||
|
if (page > maxPage) page = maxPage;
|
||||||
|
const startIndex = (page - 1) * pageLength;
|
||||||
|
return {
|
||||||
|
items: items.length > pageLength ? items.slice(startIndex, startIndex + pageLength) : items,
|
||||||
|
page,
|
||||||
|
maxPage,
|
||||||
|
pageLength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Logs;
|
Loading…
Reference in New Issue
Block a user