3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-18 15:00:00 +00:00

Add pagination to !mutes, show manual mutes separately with -manual

This commit is contained in:
Dragory 2020-05-22 22:11:08 +03:00
parent 221f996f45
commit dca678989e
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
2 changed files with 199 additions and 110 deletions

View file

@ -0,0 +1,18 @@
import humanizeDuration from "humanize-duration";
export const humanizeDurationShort = humanizeDuration.humanizer({
language: "shortEn",
languages: {
shortEn: {
y: () => "y",
mo: () => "mo",
w: () => "w",
d: () => "d",
h: () => "h",
m: () => "m",
s: () => "s",
ms: () => "ms",
},
},
spacer: "",
});

View file

@ -17,6 +17,7 @@ import {
UnknownUser, UnknownUser,
UserNotificationMethod, UserNotificationMethod,
trimLines, trimLines,
MINUTES,
} from "../utils"; } from "../utils";
import humanizeDuration from "humanize-duration"; import humanizeDuration from "humanize-duration";
import { LogType } from "../data/LogType"; import { LogType } from "../data/LogType";
@ -30,6 +31,7 @@ import { Case } from "../data/entities/Case";
import * as t from "io-ts"; import * as t from "io-ts";
import { ERRORS, RecoverablePluginError } from "../RecoverablePluginError"; import { ERRORS, RecoverablePluginError } from "../RecoverablePluginError";
import { GuildArchives } from "src/data/GuildArchives"; import { GuildArchives } from "src/data/GuildArchives";
import { humanizeDurationShort } from "../humanizeDurationShort";
const ConfigSchema = t.type({ const ConfigSchema = t.type({
mute_role: tNullable(t.string), mute_role: tNullable(t.string),
@ -378,10 +380,17 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
options: [ options: [
{ {
name: "age", name: "age",
shortcut: "a",
type: "delay", type: "delay",
}, },
{ {
name: "left", name: "left",
shortcut: "l",
isSwitch: true,
},
{
name: "manual",
shortcut: "m",
isSwitch: true, isSwitch: true,
}, },
{ {
@ -392,11 +401,21 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
], ],
}) })
@d.permission("can_view_list") @d.permission("can_view_list")
protected async muteListCmd(msg: Message, args: { age?: number; left?: boolean; export?: boolean }) { protected async muteListCmd(
const lines = []; msg: Message,
args: { age?: number; left?: boolean; manual?: boolean; export?: boolean },
) {
const listMessagePromise = msg.channel.createMessage("Loading mutes...");
const mutesPerPage = 10;
let totalMutes = 0;
let hasFilters = false;
// Create a loading message as this can potentially take some time let hasReactions = false;
const loadingMessage = await msg.channel.createMessage("Loading mutes..."); let clearReactionsFn;
let clearReactionsTimeout;
const clearReactionsDebounce = 5 * MINUTES;
let lines = [];
// Active, logged mutes // Active, logged mutes
const activeMutes = await this.mutes.getActiveMutes(); const activeMutes = await this.mutes.getActiveMutes();
@ -409,8 +428,27 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
return a.expires_at > b.expires_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 = [];
const muteRole = this.getConfig().mute_role;
if (muteRole) {
this.guild.members.forEach(member => {
if (muteUserIds.has(member.id)) return;
if (member.roles.includes(muteRole)) 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 filteredMutes: IMuteWithDetails[] = activeMutes;
let hasFilters = false;
let bannedIds: string[] = null; let bannedIds: string[] = null;
// Filter: mute age // Filter: mute age
@ -448,15 +486,14 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
hasFilters = true; hasFilters = true;
} }
// Mute count totalMutes = filteredMutes.length;
let totalMutes = filteredMutes.length;
// Create a message lines for each mute // Create a message line for each mute
const caseIds = filteredMutes.map(m => m.case_id).filter(v => !!v); const caseIds = filteredMutes.map(m => m.case_id).filter(v => !!v);
const muteCases = caseIds.length ? await this.cases.get(caseIds) : []; const muteCases = caseIds.length ? await this.cases.get(caseIds) : [];
const muteCasesById = muteCases.reduce((map, c) => map.set(c.id, c), new Map()); const muteCasesById = muteCases.reduce((map, c) => map.set(c.id, c), new Map());
for (const mute of filteredMutes) { lines = filteredMutes.map(mute => {
const user = this.bot.users.get(mute.user_id); const user = this.bot.users.get(mute.user_id);
const username = user ? `${user.username}#${user.discriminator}` : "Unknown#0000"; const username = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
const theCase = muteCasesById.get(mute.case_id); const theCase = muteCasesById.get(mute.case_id);
@ -466,74 +503,108 @@ export class MutesPlugin extends ZeppelinPlugin<TConfigSchema> {
if (mute.expires_at) { if (mute.expires_at) {
const timeUntilExpiry = moment().diff(moment(mute.expires_at, DBDateFormat)); const timeUntilExpiry = moment().diff(moment(mute.expires_at, DBDateFormat));
const humanizedTime = humanizeDuration(timeUntilExpiry, { largest: 2, round: true }); const humanizedTime = humanizeDurationShort(timeUntilExpiry, { largest: 2, round: true });
line += ` ⏰ Expires in ${humanizedTime}`; line += ` ⏰ Expires in ${humanizedTime}`;
} else { } else {
line += ` ⏰ Doesn't expire`; line += ` ⏰ Indefinite`;
} }
const timeFromMute = moment(mute.created_at, DBDateFormat).diff(moment()); const timeFromMute = moment(mute.created_at, DBDateFormat).diff(moment());
const humanizedTimeFromMute = humanizeDuration(timeFromMute, { largest: 2, round: true }); const humanizedTimeFromMute = humanizeDurationShort(timeFromMute, { largest: 2, round: true });
line += ` 🕒 Muted ${humanizedTimeFromMute} ago`; line += ` 🕒 Muted ${humanizedTimeFromMute} ago`;
if (mute.banned) { if (mute.banned) {
line += ` 🔨 User was banned`; line += ` 🔨 Banned`;
} else if (!mute.member) { } else if (!mute.member) {
line += ` ❌ Has left the server`; line += ` ❌ Left server`;
} }
lines.push(line); return line;
}
// Find manually added mute roles and create a mesage line for each (but only if no filters have been specified)
if (!hasFilters) {
const muteUserIds = activeMutes.reduce((set, m) => set.add(m.user_id), new Set());
const manuallyMutedMembers = [];
const muteRole = this.getConfig().mute_role;
if (muteRole) {
this.guild.members.forEach(member => {
if (muteUserIds.has(member.id)) return;
if (member.roles.includes(muteRole)) manuallyMutedMembers.push(member);
}); });
} }
totalMutes += manuallyMutedMembers.length; const originalLines = Array.from(lines);
for (let i = 0; i < 20; i++) {
lines.push( lines = [...lines, ...originalLines];
...manuallyMutedMembers.map(member => {
return `<@!${member.id}> (**${member.user.username}#${member.user.discriminator}**, \`${member.id}\`) 🔧 Manual mute`;
}),
);
} }
totalMutes = lines.length;
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; let message;
if (totalMutes > 0) { if (args.manual) {
message = hasFilters message = `Showing manual mutes ${pageRangeText}:`;
? `Results (${totalMutes} total):\n\n${lines.join("\n")}`.trim() } else if (hasFilters) {
: `Active mutes (${totalMutes} total):\n\n${lines.join("\n")}`.trim(); message = `Showing filtered active mutes ${pageRangeText}:`;
} else { } else {
message = hasFilters ? "No mutes found with the specified filters!" : "No active mutes!"; message = `Showing active mutes ${pageRangeText}:`;
} }
if (args.export) { message += "\n\n" + pageLines.join("\n");
const archiveId = await this.archives.create(trimLines(message), moment().add(1, "hour"));
listMessage.edit(message);
bumpClearReactionsTimeout();
};
const bumpClearReactionsTimeout = () => {
if (!hasReactions) return;
clearTimeout(clearReactionsTimeout);
clearReactionsTimeout = setTimeout(clearReactionsFn, clearReactionsDebounce);
};
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 this.archives.create(lines.join("\n"), moment().add(1, "hour"));
const url = await this.archives.getUrl(this.knub.getGlobalConfig().url, archiveId); const url = await this.archives.getUrl(this.knub.getGlobalConfig().url, archiveId);
await msg.channel.createMessage(`Exported mutes results: ${url}`); await listMessage.edit(`Exported mutes: ${url}`);
} else {
drawListPage(1);
return; if (totalPages > 1) {
hasReactions = true;
listMessage.addReaction("⬅");
listMessage.addReaction("➡");
const removeListenerFn = this.on("messageReactionAdd", (rMsg: Message, emoji, userId) => {
if (rMsg.id !== listMessage.id) return;
if (userId !== msg.author.id) return;
if (!["⬅", "➡"].includes(emoji.name)) return;
if (emoji.name === "⬅" && currentPage > 1) {
drawListPage(currentPage - 1);
} else if (emoji.name === "➡" && currentPage < totalPages) {
drawListPage(currentPage + 1);
} }
await loadingMessage.delete().catch(noop); rMsg.removeReaction(emoji.name, userId).catch(noop);
const chunks = chunkMessageLines(message); });
for (const chunk of chunks) {
msg.channel.createMessage(chunk);
}
// let the user know we are done clearReactionsFn = () => {
if (chunks.length > 2) { listMessage.removeReactions().catch(noop);
this.sendSuccessMessage(msg.channel, "All mutes for the specified filters posted!"); removeListenerFn();
};
bumpClearReactionsTimeout();
}
} }
} }