mvp & email signature

This commit is contained in:
2026-04-21 16:15:18 +00:00
parent 071fae2ea3
commit bf901039bc
15 changed files with 137 additions and 1291 deletions

View File

@@ -1,195 +0,0 @@
/**
* Account info command: look up website User by email or Discord ID,
* show ephemeral embed with option to send transcript to account info channel.
*/
const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const { CONFIG } = require('../config');
const { mongoose } = require('../db-connection');
const { logSecurity } = require('../services/debugLog');
const { enqueueSend } = require('../services/channelQueue');
const { isStaff } = require('../utils');
const User = mongoose.model('User');
const BUTTON_PREFIX = 'send_account_info_';
const MAX_CUSTOM_ID_LENGTH = 100;
function buildAccountInfoEmbed(user, requestedBy = null) {
const embed = new EmbedBuilder()
.setTitle('Account Info')
.setColor(CONFIG.EMBED_COLOR_INFO)
.setTimestamp();
embed.addFields({
name: 'Email',
value: user.email || '*not set*',
inline: true
});
embed.addFields({
name: 'Discord ID',
value: user.discordID ? `<@${user.discordID}>` : '*not set*',
inline: true
});
embed.addFields({
name: 'Customer ID',
value: user.customerId || '*not set*',
inline: true
});
const servers = user.servers || [];
const serverOrder = user.serverOrder || [];
const ordered = serverOrder.length
? serverOrder.map(id => servers.find(s => s._id && s._id.toString() === id) || servers[serverOrder.indexOf(id)]).filter(Boolean)
: servers;
if (ordered.length === 0) {
embed.addFields({
name: 'Servers',
value: '*No servers*',
inline: false
});
} else {
ordered.forEach((server, i) => {
const n = i + 1;
embed.addFields({
name: `Server ${n} Game`,
value: server.game || '*not set*',
inline: true
});
embed.addFields({
name: `Server ${n} IP`,
value: server.ip || '*not set*',
inline: true
});
embed.addFields({
name: `Server ${n} Port`,
value: server.serverPort != null ? String(server.serverPort) : '*not set*',
inline: true
});
});
}
if (requestedBy) {
embed.setFooter({ text: `Requested by ${requestedBy}` });
}
return embed;
}
async function handleAccountInfoCommand(interaction) {
const subcommand = interaction.options.getSubcommand();
let user = null;
if (subcommand === 'email') {
const email = (interaction.options.getString('email') || '').trim().toLowerCase();
if (!email) {
return interaction.reply({ content: 'Please provide an email.', ephemeral: true });
}
user = await User.findOne({ email }).lean();
} else if (subcommand === 'discord') {
const target = interaction.options.getUser('user');
if (!target) {
return interaction.reply({ content: 'Please provide a Discord user.', ephemeral: true });
}
user = await User.findOne({ discordID: target.id }).lean();
}
if (!user) {
return interaction.reply({
content: subcommand === 'email' ? 'No account found for that email.' : 'No account found for that Discord user/ID.',
ephemeral: true
});
}
const identifier = subcommand === 'email'
? interaction.options.getString('email')
: interaction.options.getUser('user')?.tag || 'unknown';
logSecurity('Account lookup', interaction.user, `lookup: ${subcommand}${identifier}`, null, 0x0099ff).catch(() => {});
const embed = buildAccountInfoEmbed(user, interaction.user.tag);
const components = [];
if (CONFIG.ACCOUNT_INFO_CHANNEL_ID) {
const safeEmail = (user.email || '').slice(0, 50);
const safeDiscordId = (user.discordID || '').slice(0, 50);
const customId = `${BUTTON_PREFIX}discord:${safeDiscordId}`;
if (customId.length <= MAX_CUSTOM_ID_LENGTH) {
components.push(
new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId(customId)
.setLabel('Send to account info channel')
.setStyle(ButtonStyle.Secondary)
)
);
}
}
await interaction.reply({
embeds: [embed],
components,
ephemeral: true
});
}
async function handleSendAccountInfoToChannel(interaction) {
if (!interaction.isButton() || !interaction.customId.startsWith(BUTTON_PREFIX)) return false;
// Dispatched directly from interactionCreate — no upstream command-level staff gate here, so enforce it.
if (!isStaff(interaction.member)) {
logSecurity('Unauthorized account-info button', interaction.user, `non-staff pressed ${interaction.customId}`, null, 0xff0000).catch(() => {});
await interaction.reply({ content: 'You do not have permission to do that.', ephemeral: true }).catch(() => {});
return true;
}
const payload = interaction.customId.slice(BUTTON_PREFIX.length);
const [type, value] = payload.includes(':') ? payload.split(':') : [payload, ''];
let user = null;
if (type === 'email') {
const email = Buffer.from(value, 'base64').toString('utf8').toLowerCase();
user = await User.findOne({ email }).lean();
} else if (type === 'discord' && value) {
user = await User.findOne({ discordID: value }).lean();
}
if (!user) {
await interaction.update({ content: 'Account no longer found.', components: [] }).catch(() =>
interaction.followUp({ content: 'Account no longer found.', ephemeral: true })
);
return true;
}
if (!CONFIG.ACCOUNT_INFO_CHANNEL_ID) {
await interaction.update({ content: 'Account info channel is not configured.', components: [] }).catch(() =>
interaction.followUp({ content: 'Account info channel is not configured.', ephemeral: true })
);
return true;
}
const channel = await interaction.client.channels.fetch(CONFIG.ACCOUNT_INFO_CHANNEL_ID).catch(() => null);
if (!channel) {
await interaction.update({ content: 'Could not find account info channel.', components: [] }).catch(() =>
interaction.followUp({ content: 'Could not find account info channel.', ephemeral: true })
);
return true;
}
const embed = buildAccountInfoEmbed(user, `${interaction.user.tag} (from ticket)`);
await enqueueSend(channel, { embeds: [embed] });
await interaction.update({
content: 'Account info sent to account transcript channel.',
components: []
}).catch(() =>
interaction.followUp({ content: 'Account info sent to account transcript channel.', ephemeral: true })
);
return true;
}
module.exports = {
buildAccountInfoEmbed,
handleAccountInfoCommand,
handleSendAccountInfoToChannel,
BUTTON_PREFIX
};

View File

@@ -30,7 +30,6 @@ const { logError, logSystem } = require('../services/debugLog');
const Ticket = mongoose.model('Ticket');
const Transcript = mongoose.model('Transcript');
const Tag = mongoose.model('Tag');
const User = mongoose.model('User');
/**
* Main button/modal handler called from interactionCreate.

View File

@@ -21,13 +21,11 @@ const { enqueueRename, enqueueMove, enqueueSend } = require('../services/channel
const { setNotifyDm } = require('../services/staffSettings');
const { trackInteraction, trackError, getAnalyticsSummary } = require('./analytics');
const { logTicketEvent, logSecurity, logError } = require('../services/debugLog');
const { handleAccountInfoCommand } = require('./accountinfo');
const { handleSetupCommand } = require('./setup');
const { pendingCloses } = require('./pendingCloses');
const Ticket = mongoose.model('Ticket');
const Tag = mongoose.model('Tag');
const User = mongoose.model('User');
/**
* True if member has the support role (ROLE_ID_TO_PING) or any ADDITIONAL_STAFF_ROLES.
@@ -800,12 +798,6 @@ async function handleCommand(interaction) {
return;
}
// /accountinfo
if (interaction.commandName === 'accountinfo') {
await handleAccountInfoCommand(interaction);
return;
}
// /help
if (interaction.commandName === 'help') {
const embed = new EmbedBuilder()
@@ -818,7 +810,7 @@ async function handleCommand(interaction) {
},
{
name: 'Ticket Management',
value: '`/transfer @staff` - Transfer ticket to another staff member\n`/move #category` - Move ticket to another category\n`/force-close` - Force close ticket without confirmation\n`/topic <text>` - Set ticket topic/description\n`/accountinfo email` - Look up website account by email\n`/accountinfo discord @user` - Look up website account by Discord user'
value: '`/transfer @staff` - Transfer ticket to another staff member\n`/move #category` - Move ticket to another category\n`/force-close` - Force close ticket without confirmation\n`/topic <text>` - Set ticket topic/description'
},
{
name: 'Saved Responses',