3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-10 12:25:02 +00:00
zeppelin/backend/src/plugins/Mutes/commands/MutesCmd.ts
2021-07-25 14:32:08 +02:00

248 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { GuildMember, MessageActionRow, MessageButton, MessageComponentInteraction, Snowflake } from "discord.js";
import moment from "moment-timezone";
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { humanizeDurationShort } from "../../../humanizeDurationShort";
import { getBaseUrl } from "../../../pluginUtils";
import { DBDateFormat, MINUTES, resolveMember } from "../../../utils";
import { IMuteWithDetails, mutesCmd } from "../types";
export const MutesCmd = mutesCmd({
trigger: "mutes",
permission: "can_view_list",
signature: {
age: ct.delay({
option: true,
shortcut: "a",
}),
left: ct.switchOption({ def: false, shortcut: "l" }),
manual: ct.switchOption({ def: false, shortcut: "m" }),
export: ct.switchOption({ def: false, shortcut: "e" }),
},
async run({ pluginData, message: msg, args }) {
const listMessagePromise = msg.channel.send("Loading mutes...");
const mutesPerPage = 10;
let totalMutes = 0;
let hasFilters = false;
let stopCollectionFn = () => {
return;
};
let stopCollectionTimeout: NodeJS.Timeout;
const stopCollectionDebounce = 5 * MINUTES;
const bumpCollectionTimeout = () => {
clearTimeout(stopCollectionTimeout);
stopCollectionTimeout = setTimeout(stopCollectionFn, stopCollectionDebounce);
};
let lines: string[] = [];
// Active, logged mutes
const activeMutes = await pluginData.state.mutes.getActiveMutes();
activeMutes.sort((a, b) => {
if (a.expires_at == null && b.expires_at != null) return 1;
if (b.expires_at == null && a.expires_at != null) return -1;
if (a.expires_at == null && b.expires_at == null) {
return a.created_at > b.created_at ? -1 : 1;
}
return a.expires_at! > b.expires_at! ? 1 : -1;
});
if (args.manual) {
// Show only manual mutes (i.e. "Muted" role added without a logged mute)
const muteUserIds = new Set(activeMutes.map(m => m.user_id));
const manuallyMutedMembers: GuildMember[] = [];
const muteRole = pluginData.config.get().mute_role;
if (muteRole) {
pluginData.guild.members.cache.forEach(member => {
if (muteUserIds.has(member.id)) return;
if (member.roles.cache.has(muteRole as Snowflake)) manuallyMutedMembers.push(member);
});
}
totalMutes = manuallyMutedMembers.length;
lines = manuallyMutedMembers.map(member => {
return `<@!${member.id}> (**${member.user.username}#${member.user.discriminator}**, \`${member.id}\`) 🔧 Manual mute`;
});
} else {
// Show filtered active mutes (but not manual mutes)
let filteredMutes: IMuteWithDetails[] = activeMutes;
let bannedIds: string[] | null = null;
// Filter: mute age
if (args.age) {
const cutoff = moment
.utc()
.subtract(args.age, "ms")
.format(DBDateFormat);
filteredMutes = filteredMutes.filter(m => m.created_at <= cutoff);
hasFilters = true;
}
// Fetch some extra details for each mute: the muted member, and whether they've been banned
for (const [index, mute] of filteredMutes.entries()) {
const muteWithDetails = { ...mute };
const member = await resolveMember(pluginData.client, pluginData.guild, mute.user_id);
if (!member) {
if (!bannedIds) {
const bans = await pluginData.guild.bans.fetch({ cache: true });
bannedIds = bans.map(u => u.user.id);
}
muteWithDetails.banned = bannedIds.includes(mute.user_id);
} else {
muteWithDetails.member = member;
}
filteredMutes[index] = muteWithDetails;
}
// Filter: left the server
if (args.left != null) {
filteredMutes = filteredMutes.filter(m => (args.left && !m.member) || (!args.left && m.member));
hasFilters = true;
}
totalMutes = filteredMutes.length;
// Create a message line for each mute
const caseIds = filteredMutes.map(m => m.case_id).filter(v => !!v);
const muteCases = caseIds.length ? await pluginData.state.cases.get(caseIds) : [];
const muteCasesById = muteCases.reduce((map, c) => map.set(c.id, c), new Map());
lines = filteredMutes.map(mute => {
const user = pluginData.client.users.resolve(mute.user_id as Snowflake);
const username = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
const theCase = muteCasesById.get(mute.case_id);
const caseName = theCase ? `Case #${theCase.case_number}` : "No case";
let line = `<@!${mute.user_id}> (**${username}**, \`${mute.user_id}\`) 📋 ${caseName}`;
if (mute.expires_at) {
const timeUntilExpiry = moment.utc().diff(moment.utc(mute.expires_at, DBDateFormat));
const humanizedTime = humanizeDurationShort(timeUntilExpiry, { largest: 2, round: true });
line += ` ⏰ Expires in ${humanizedTime}`;
} else {
line += ` ⏰ Indefinite`;
}
const timeFromMute = moment.utc(mute.created_at, DBDateFormat).diff(moment.utc());
const humanizedTimeFromMute = humanizeDurationShort(timeFromMute, { largest: 2, round: true });
line += ` 🕒 Muted ${humanizedTimeFromMute} ago`;
if (mute.banned) {
line += ` 🔨 Banned`;
} else if (!mute.member) {
line += ` ❌ Left server`;
}
return line;
});
}
const listMessage = await listMessagePromise;
let currentPage = 1;
const totalPages = Math.ceil(lines.length / mutesPerPage);
const drawListPage = async page => {
page = Math.max(1, Math.min(totalPages, page));
currentPage = page;
const pageStart = (page - 1) * mutesPerPage;
const pageLines = lines.slice(pageStart, pageStart + mutesPerPage);
const pageRangeText = `${pageStart + 1}${pageStart + pageLines.length} of ${totalMutes}`;
let message;
if (args.manual) {
message = `Showing manual mutes ${pageRangeText}:`;
} else if (hasFilters) {
message = `Showing filtered active mutes ${pageRangeText}:`;
} else {
message = `Showing active mutes ${pageRangeText}:`;
}
message += "\n\n" + pageLines.join("\n");
listMessage.edit(message);
bumpCollectionTimeout();
};
if (totalMutes === 0) {
if (args.manual) {
listMessage.edit("No manual mutes found!");
} else if (hasFilters) {
listMessage.edit("No mutes found with the specified filters!");
} else {
listMessage.edit("No active mutes!");
}
} else if (args.export) {
const archiveId = await pluginData.state.archives.create(lines.join("\n"), moment.utc().add(1, "hour"));
const baseUrl = getBaseUrl(pluginData);
const url = await pluginData.state.archives.getUrl(baseUrl, archiveId);
await listMessage.edit(`Exported mutes: ${url}`);
} else {
drawListPage(1);
if (totalPages > 1) {
const idMod = `${listMessage.id}:muteList`;
const buttons: MessageButton[] = [];
buttons.push(
new MessageButton()
.setStyle("SECONDARY")
.setEmoji("⬅")
.setCustomId(`previousButton:${idMod}`),
);
buttons.push(
new MessageButton()
.setStyle("SECONDARY")
.setEmoji("➡")
.setCustomId(`nextButton:${idMod}`),
);
const row = new MessageActionRow().addComponents(buttons);
await listMessage.edit({ components: [row] });
const filter = (iac: MessageComponentInteraction) => iac.message.id === listMessage.id;
const collector = listMessage.createMessageComponentCollector({
filter,
time: stopCollectionDebounce,
});
collector.on("collect", async (interaction: MessageComponentInteraction) => {
if (msg.author.id !== interaction.user.id) {
interaction.reply({ content: `You are not permitted to use these buttons.`, ephemeral: true });
} else {
collector.resetTimer();
if (interaction.customId === `previousButton:${idMod}` && currentPage > 1) {
await interaction.deferUpdate();
await drawListPage(currentPage - 1);
} else if (interaction.customId === `nextButton:${idMod}` && currentPage < totalPages) {
await interaction.deferUpdate();
await drawListPage(currentPage + 1);
} else {
await interaction.deferUpdate();
}
}
});
stopCollectionFn = async () => {
collector.stop();
await listMessage.edit({ content: listMessage.content, components: [] });
};
bumpCollectionTimeout();
}
}
},
});