ramirez/src/data/threads.js

210 lines
5.9 KiB
JavaScript

const Eris = require('eris');
const transliterate = require('transliteration');
const moment = require('moment');
const uuid = require('uuid');
const humanizeDuration = require('humanize-duration');
const bot = require('../bot');
const knex = require('../knex');
const config = require('../config');
const utils = require('../utils');
const Thread = require('./Thread');
const {THREAD_STATUS} = require('./constants');
/**
* @param {String} id
* @returns {Promise<Thread>}
*/
async function findById(id) {
const thread = await knex('threads')
.where('id', id)
.first();
return (thread ? new Thread(thread) : null);
}
/**
* @param {String} userId
* @returns {Promise<Thread>}
*/
async function findOpenThreadByUserId(userId) {
const thread = await knex('threads')
.where('user_id', userId)
.where('status', THREAD_STATUS.OPEN)
.first();
return (thread ? new Thread(thread) : null);
}
/**
* Creates a new modmail thread for the specified user
* @param {Eris.User} user
* @returns {Promise<Thread>}
* @throws {Error}
*/
async function createNewThreadForUser(user) {
const existingThread = await findOpenThreadByUserId(user.id);
if (existingThread) {
throw new Error('Attempted to create a new thread for a user with an existing open thread!');
}
// Use the user's name+discrim for the thread channel's name
// Channel names are particularly picky about what characters they allow, so we gotta do some clean-up
let cleanName = transliterate.slugify(user.username);
if (cleanName === '') cleanName = 'unknown';
cleanName = cleanName.slice(0, 95); // Make sure the discrim fits
const channelName = `${cleanName}-${user.discriminator}`;
console.log(`[NOTE] Creating new thread channel ${channelName}`);
// Attempt to create the inbox channel for this thread
let createdChannel;
try {
createdChannel = await utils.getInboxGuild().createChannel(channelName, null, 'New ModMail thread', config.newThreadCategoryId);
} catch (err) {
console.error(`Error creating modmail channel for ${user.username}#${user.discriminator}!`);
throw err;
}
// Save the new thread in the database
const newThreadId = await createThreadInDB({
status: THREAD_STATUS.OPEN,
user_id: user.id,
user_name: `${user.username}#${user.discriminator}`,
channel_id: createdChannel.id,
created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss')
});
const newThread = await findById(newThreadId);
// Post the log link to the beginning (but don't save it in thread messages)
const logUrl = await newThread.getLogUrl();
await newThread.postNonLogMessage(`Log URL: <${logUrl}>`);
// Send auto-reply to the user
if (config.responseMessage) {
newThread.postToUser(config.responseMessage);
}
// Post some info to the beginning of the new thread
const mainGuild = utils.getMainGuild();
const member = (mainGuild ? mainGuild.members.get(user.id) : null);
if (! member) console.log(`[INFO] Member ${user.id} not found in main guild ${config.mainGuildId}`);
let mainGuildNickname = null;
if (member && member.nick) mainGuildNickname = member.nick;
else if (member && member.user) mainGuildNickname = member.user.username;
else if (member == null) mainGuildNickname = 'NOT ON SERVER';
if (mainGuildNickname == null) mainGuildNickname = 'UNKNOWN';
const userLogCount = await getClosedThreadCountByUserId(user.id);
const accountAge = humanizeDuration(Date.now() - user.createdAt, {largest: 2});
const infoHeader = `ACCOUNT AGE **${accountAge}**, ID **${user.id}**, NICKNAME **${mainGuildNickname}**, LOGS **${userLogCount}**\n-------------------------------`;
await newThread.postSystemMessage(infoHeader);
// Return the thread
return newThread;
}
/**
* Creates a new thread row in the database
* @param {Object} data
* @returns {Promise<String>} The ID of the created thread
*/
async function createThreadInDB(data) {
const threadId = uuid.v4();
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss');
const finalData = Object.assign({created_at: now, is_legacy: 0}, data, {id: threadId});
await knex('threads').insert(finalData);
return threadId;
}
/**
* @param {String} id
* @returns {Promise<Thread>}
*/
async function findById(id) {
const row = await knex('threads')
.where('id', id)
.first();
if (! row) return null;
return new Thread(row);
}
/**
* @param {String} channelId
* @returns {Promise<Thread>}
*/
async function findByChannelId(channelId) {
const thread = await knex('threads')
.where('channel_id', channelId)
.first();
return (thread ? new Thread(thread) : null);
}
/**
* @param {String} channelId
* @returns {Promise<Thread>}
*/
async function findOpenThreadByChannelId(channelId) {
const thread = await knex('threads')
.where('channel_id', channelId)
.where('status', THREAD_STATUS.OPEN)
.first();
return (thread ? new Thread(thread) : null);
}
/**
* @param {String} userId
* @returns {Promise<Thread[]>}
*/
async function getClosedThreadsByUserId(userId) {
const threads = await knex('threads')
.where('status', THREAD_STATUS.CLOSED)
.where('user_id', userId)
.select();
return threads.map(thread => new Thread(thread));
}
/**
* @param {String} userId
* @returns {Promise<number>}
*/
async function getClosedThreadCountByUserId(userId) {
const row = await knex('threads')
.where('status', THREAD_STATUS.CLOSED)
.where('user_id', userId)
.first(knex.raw('COUNT(id) AS thread_count'));
return parseInt(row.thread_count, 10);
}
async function findOrCreateThreadForUser(user) {
const existingThread = await findOpenThreadByUserId(user.id);
if (existingThread) return existingThread;
return createNewThreadForUser(user);
}
module.exports = {
findById,
findOpenThreadByUserId,
findByChannelId,
findOpenThreadByChannelId,
createNewThreadForUser,
getClosedThreadsByUserId,
findOrCreateThreadForUser,
createThreadInDB
};