canned replies, logs etc etc

This commit is contained in:
Erik 2021-06-19 21:05:32 +03:00
parent 05eb2ef0f1
commit 2a9218e7f3
No known key found for this signature in database
GPG Key ID: 7E862371D3409F16
4 changed files with 187 additions and 13 deletions

View File

@ -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;

View File

@ -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,12 +352,11 @@ 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 {
@ -363,12 +364,12 @@ class Modmail {
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;

View File

@ -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;

View 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;