mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 12:25:02 +00:00
Use server timezone and date formats in case summaries. Link to cases in case log channel from case summaries.
This commit is contained in:
parent
638f9685b1
commit
eb203a3b7a
9 changed files with 178 additions and 62 deletions
|
@ -158,32 +158,4 @@ export class GuildCases extends BaseGuildRepository {
|
||||||
case_id: caseId,
|
case_id: caseId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to the cases plugin, use server timezone + date formats
|
|
||||||
getSummaryText(theCase: Case) {
|
|
||||||
const firstNote = theCase.notes[0];
|
|
||||||
let reason = firstNote ? firstNote.body : "";
|
|
||||||
|
|
||||||
if (reason.length > CASE_SUMMARY_REASON_MAX_LENGTH) {
|
|
||||||
const match = reason.slice(CASE_SUMMARY_REASON_MAX_LENGTH, 100).match(/(?:[.,!?\s]|$)/);
|
|
||||||
const nextWhitespaceIndex = match ? CASE_SUMMARY_REASON_MAX_LENGTH + match.index : CASE_SUMMARY_REASON_MAX_LENGTH;
|
|
||||||
if (nextWhitespaceIndex < reason.length) {
|
|
||||||
reason = reason.slice(0, nextWhitespaceIndex - 1) + "...";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reason = disableLinkPreviews(reason);
|
|
||||||
|
|
||||||
const timestamp = moment.utc(theCase.created_at, DBDateFormat).format("YYYY-MM-DD");
|
|
||||||
let line = `\`[${timestamp}]\` \`Case #${theCase.case_number}\` __${CaseTypes[theCase.type]}__ ${reason}`;
|
|
||||||
if (theCase.notes.length > 1) {
|
|
||||||
line += ` *(+${theCase.notes.length - 1} ${theCase.notes.length === 2 ? "note" : "notes"})*`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theCase.is_hidden) {
|
|
||||||
line += " *(hidden)*";
|
|
||||||
}
|
|
||||||
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { CaseTypes } from "../../data/CaseTypes";
|
||||||
import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUserId";
|
import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUserId";
|
||||||
import { getCaseEmbed } from "./functions/getCaseEmbed";
|
import { getCaseEmbed } from "./functions/getCaseEmbed";
|
||||||
import { trimPluginDescription } from "../../utils";
|
import { trimPluginDescription } from "../../utils";
|
||||||
|
import { getCaseSummary } from "./functions/getCaseSummary";
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -61,6 +62,12 @@ export const CasesPlugin = zeppelinPlugin<CasesPluginType>()("cases", {
|
||||||
return getCaseEmbed(pluginData, caseOrCaseId);
|
return getCaseEmbed(pluginData, caseOrCaseId);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getCaseSummary(pluginData) {
|
||||||
|
return (caseOrCaseId: Case | number, withLinks = false) => {
|
||||||
|
return getCaseSummary(pluginData, caseOrCaseId, withLinks);
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad(pluginData) {
|
onLoad(pluginData) {
|
||||||
|
|
56
backend/src/plugins/Cases/functions/getCaseSummary.ts
Normal file
56
backend/src/plugins/Cases/functions/getCaseSummary.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { CasesPluginType } from "../types";
|
||||||
|
import { disableLinkPreviews } from "../../../utils";
|
||||||
|
import { DBDateFormat, getDateFormat } from "../../../utils/dateFormats";
|
||||||
|
import { CaseTypes } from "../../../data/CaseTypes";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
import { Case } from "../../../data/entities/Case";
|
||||||
|
import { inGuildTz } from "../../../utils/timezones";
|
||||||
|
|
||||||
|
const CASE_SUMMARY_REASON_MAX_LENGTH = 300;
|
||||||
|
|
||||||
|
export async function getCaseSummary(
|
||||||
|
pluginData: PluginData<CasesPluginType>,
|
||||||
|
caseOrCaseId: Case | number,
|
||||||
|
withLinks = false,
|
||||||
|
) {
|
||||||
|
const caseId = caseOrCaseId instanceof Case ? caseOrCaseId.id : caseOrCaseId;
|
||||||
|
|
||||||
|
const theCase = await pluginData.state.cases.with("notes").find(caseId);
|
||||||
|
|
||||||
|
const firstNote = theCase.notes[0];
|
||||||
|
let reason = firstNote ? firstNote.body : "";
|
||||||
|
|
||||||
|
if (reason.length > CASE_SUMMARY_REASON_MAX_LENGTH) {
|
||||||
|
const match = reason.slice(CASE_SUMMARY_REASON_MAX_LENGTH, 100).match(/(?:[.,!?\s]|$)/);
|
||||||
|
const nextWhitespaceIndex = match ? CASE_SUMMARY_REASON_MAX_LENGTH + match.index : CASE_SUMMARY_REASON_MAX_LENGTH;
|
||||||
|
if (nextWhitespaceIndex < reason.length) {
|
||||||
|
reason = reason.slice(0, nextWhitespaceIndex - 1) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reason = disableLinkPreviews(reason);
|
||||||
|
|
||||||
|
const timestamp = moment.utc(theCase.created_at, DBDateFormat);
|
||||||
|
const prettyTimestamp = inGuildTz(pluginData, timestamp).format(getDateFormat(pluginData, "date"));
|
||||||
|
|
||||||
|
let caseTitle = `\`Case #${theCase.case_number}\``;
|
||||||
|
if (withLinks && theCase.log_message_id) {
|
||||||
|
const [channelId, messageId] = theCase.log_message_id.split("-");
|
||||||
|
caseTitle = `[${caseTitle}](https://discord.com/channels/${pluginData.guild.id}/${channelId}/${messageId})`;
|
||||||
|
} else {
|
||||||
|
caseTitle = `\`${caseTitle}\``;
|
||||||
|
}
|
||||||
|
const caseType = `__${CaseTypes[theCase.type]}__`;
|
||||||
|
|
||||||
|
let line = `\`[${prettyTimestamp}]\` ${caseTitle} ${caseType} ${reason}`;
|
||||||
|
if (theCase.notes.length > 1) {
|
||||||
|
line += ` *(+${theCase.notes.length - 1} ${theCase.notes.length === 2 ? "note" : "notes"})*`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theCase.is_hidden) {
|
||||||
|
line += " *(hidden)*";
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import { getLogMessage } from "./util/getLogMessage";
|
||||||
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
||||||
import { disableCodeBlocks } from "../../utils";
|
import { disableCodeBlocks } from "../../utils";
|
||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
|
import { CasesPlugin } from "../Cases/CasesPlugin";
|
||||||
|
|
||||||
const defaultOptions: PluginOptions<LogsPluginType> = {
|
const defaultOptions: PluginOptions<LogsPluginType> = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -48,6 +49,7 @@ export const LogsPlugin = zeppelinPlugin<LogsPluginType>()("logs", {
|
||||||
prettyName: "Logs",
|
prettyName: "Logs",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dependencies: [CasesPlugin],
|
||||||
configSchema: ConfigSchema,
|
configSchema: ConfigSchema,
|
||||||
defaultOptions,
|
defaultOptions,
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { stripObjectToScalars } from "src/utils";
|
||||||
import { LogType } from "src/data/LogType";
|
import { LogType } from "src/data/LogType";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
|
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||||
|
|
||||||
export const LogsGuildMemberAddEvt = logsEvent({
|
export const LogsGuildMemberAddEvt = logsEvent({
|
||||||
event: "guildMemberAdd",
|
event: "guildMemberAdd",
|
||||||
|
@ -29,8 +30,9 @@ export const LogsGuildMemberAddEvt = logsEvent({
|
||||||
if (cases.length) {
|
if (cases.length) {
|
||||||
const recentCaseLines = [];
|
const recentCaseLines = [];
|
||||||
const recentCases = cases.slice(0, 2);
|
const recentCases = cases.slice(0, 2);
|
||||||
|
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||||
for (const theCase of recentCases) {
|
for (const theCase of recentCases) {
|
||||||
recentCaseLines.push(pluginData.state.cases.getSummaryText(theCase));
|
recentCaseLines.push(await casesPlugin.getCaseSummary(theCase, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
let recentCaseSummary = recentCaseLines.join("\n");
|
let recentCaseSummary = recentCaseLines.join("\n");
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import { modActionsCommand } from "../types";
|
import { modActionsCommand } from "../types";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { sendErrorMessage } from "../../../pluginUtils";
|
import { sendErrorMessage } from "../../../pluginUtils";
|
||||||
import { trimLines, createChunkedMessage } from "src/utils";
|
import { trimLines, createChunkedMessage, emptyEmbedValue } from "src/utils";
|
||||||
|
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||||
|
import { asyncMap } from "../../../utils/async";
|
||||||
|
import { EmbedOptions } from "eris";
|
||||||
|
import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields";
|
||||||
|
import { getDefaultPrefix } from "knub/dist/commands/commandUtils";
|
||||||
|
import { getGuildPrefix } from "../../../utils/getGuildPrefix";
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
mod: ct.member({ option: true }),
|
mod: ct.member({ option: true }),
|
||||||
|
@ -28,16 +34,26 @@ export const CasesModCmd = modActionsCommand({
|
||||||
if (recentCases.length === 0) {
|
if (recentCases.length === 0) {
|
||||||
sendErrorMessage(pluginData, msg.channel, `No cases by **${modName}**`);
|
sendErrorMessage(pluginData, msg.channel, `No cases by **${modName}**`);
|
||||||
} else {
|
} else {
|
||||||
const lines = recentCases.map(c => pluginData.state.cases.getSummaryText(c));
|
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||||
const finalMessage = trimLines(`
|
const lines = await asyncMap(recentCases, c => casesPlugin.getCaseSummary(c, true));
|
||||||
Most recent 5 cases by **${modName}**:
|
const prefix = getGuildPrefix(pluginData);
|
||||||
|
const embed: EmbedOptions = {
|
||||||
${lines.join("\n")}
|
author: {
|
||||||
|
name: `Most recent 5 cases by ${modName}`,
|
||||||
Use the \`case <num>\` command to see more info about individual cases
|
icon_url: mod ? mod.avatarURL || mod.defaultAvatarURL : undefined,
|
||||||
Use the \`cases <user>\` command to see a specific user's cases
|
},
|
||||||
`);
|
fields: [
|
||||||
createChunkedMessage(msg.channel, finalMessage);
|
...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")),
|
||||||
|
{
|
||||||
|
name: emptyEmbedValue,
|
||||||
|
value: trimLines(`
|
||||||
|
Use \`${prefix}case <num>\` to see more information about an individual case
|
||||||
|
Use \`${prefix}cases <user>\` to see a specific user's cases
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
msg.channel.createMessage({ embed });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,19 @@ import { modActionsCommand } from "../types";
|
||||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||||
import { sendErrorMessage } from "../../../pluginUtils";
|
import { sendErrorMessage } from "../../../pluginUtils";
|
||||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||||
import { UnknownUser, multiSorter, trimLines, createChunkedMessage, resolveUser } from "src/utils";
|
import {
|
||||||
|
UnknownUser,
|
||||||
|
multiSorter,
|
||||||
|
trimLines,
|
||||||
|
createChunkedMessage,
|
||||||
|
resolveUser,
|
||||||
|
emptyEmbedValue,
|
||||||
|
chunkArray,
|
||||||
|
} from "src/utils";
|
||||||
|
import { getGuildPrefix } from "../../../utils/getGuildPrefix";
|
||||||
|
import { EmbedOptions, User } from "eris";
|
||||||
|
import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields";
|
||||||
|
import { asyncMap } from "../../../utils/async";
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }),
|
expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }),
|
||||||
|
@ -54,30 +66,50 @@ export const CasesUserCmd = modActionsCommand({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compact view (= regular message with a preview of each case)
|
// Compact view (= regular message with a preview of each case)
|
||||||
const lines = [];
|
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||||
for (const theCase of casesToDisplay) {
|
const lines = await asyncMap(casesToDisplay, c => casesPlugin.getCaseSummary(c, true));
|
||||||
theCase.notes.sort(multiSorter(["created_at", "id"]));
|
|
||||||
const caseSummary = pluginData.state.cases.getSummaryText(theCase);
|
|
||||||
lines.push(caseSummary);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!args.hidden && hiddenCases.length) {
|
const prefix = getGuildPrefix(pluginData);
|
||||||
|
const linesPerChunk = 15;
|
||||||
|
const lineChunks = chunkArray(lines, linesPerChunk);
|
||||||
|
|
||||||
|
const footerField = {
|
||||||
|
name: emptyEmbedValue,
|
||||||
|
value: trimLines(`
|
||||||
|
Use \`${prefix}case <num>\` to see more information about an individual case
|
||||||
|
`),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [i, linesInChunk] of lineChunks.entries()) {
|
||||||
|
const isLastChunk = i === lineChunks.length - 1;
|
||||||
|
|
||||||
|
if (isLastChunk && !args.hidden && hiddenCases.length) {
|
||||||
if (hiddenCases.length === 1) {
|
if (hiddenCases.length === 1) {
|
||||||
lines.push(`*+${hiddenCases.length} hidden case, use "-hidden" to show it*`);
|
linesInChunk.push(`*+${hiddenCases.length} hidden case, use "-hidden" to show it*`);
|
||||||
} else {
|
} else {
|
||||||
lines.push(`*+${hiddenCases.length} hidden cases, use "-hidden" to show them*`);
|
linesInChunk.push(`*+${hiddenCases.length} hidden cases, use "-hidden" to show them*`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalMessage = trimLines(`
|
const chunkStart = i * linesPerChunk + 1;
|
||||||
Cases for **${userName}**:
|
const chunkEnd = Math.min((i + 1) * linesPerChunk, lines.length);
|
||||||
|
|
||||||
${lines.join("\n")}
|
const embed: EmbedOptions = {
|
||||||
|
author: {
|
||||||
|
name:
|
||||||
|
lineChunks.length === 1
|
||||||
|
? `Cases for ${userName} (${lines.length} total)`
|
||||||
|
: `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`,
|
||||||
|
icon_url: user instanceof User ? user.avatarURL || user.defaultAvatarURL : undefined,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")),
|
||||||
|
...(isLastChunk ? [footerField] : []),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
Use the \`case <num>\` command to see more info about individual cases
|
msg.channel.createMessage({ embed });
|
||||||
`);
|
}
|
||||||
|
|
||||||
createChunkedMessage(msg.channel, finalMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
23
backend/src/utils/getChunkedEmbedFields.ts
Normal file
23
backend/src/utils/getChunkedEmbedFields.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { EmbedField } from "eris";
|
||||||
|
import { chunkMessageLines, emptyEmbedValue } from "../utils";
|
||||||
|
|
||||||
|
export function getChunkedEmbedFields(name: string, value: string, inline?: boolean): EmbedField[] {
|
||||||
|
const fields: EmbedField[] = [];
|
||||||
|
|
||||||
|
const chunks = chunkMessageLines(value, 1014);
|
||||||
|
for (let i = 0; i < chunks.length; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
fields.push({
|
||||||
|
name,
|
||||||
|
value: chunks[i],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fields.push({
|
||||||
|
name: emptyEmbedValue,
|
||||||
|
value: chunks[i],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
6
backend/src/utils/getGuildPrefix.ts
Normal file
6
backend/src/utils/getGuildPrefix.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { getDefaultPrefix } from "knub/dist/commands/commandUtils";
|
||||||
|
|
||||||
|
export function getGuildPrefix(pluginData: PluginData<any>) {
|
||||||
|
return pluginData.guildConfig.prefix || getDefaultPrefix(pluginData.client);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue