diff --git a/backend/src/plugins/Logs/LogsPlugin.ts b/backend/src/plugins/Logs/LogsPlugin.ts index 92858052..f643275c 100644 --- a/backend/src/plugins/Logs/LogsPlugin.ts +++ b/backend/src/plugins/Logs/LogsPlugin.ts @@ -1,6 +1,6 @@ import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { PluginOptions } from "knub"; -import { ConfigSchema, LogsPluginType } from "./types"; +import { ConfigSchema, FORMAT_NO_TIMESTAMP, LogsPluginType } from "./types"; import DefaultLogMessages from "../../data/DefaultLogMessages.json"; import { GuildLogs } from "../../data/GuildLogs"; import { GuildSavedMessages } from "../../data/GuildSavedMessages"; @@ -28,10 +28,12 @@ const defaultOptions: PluginOptions = { config: { channels: {}, format: { - timestamp: "YYYY-MM-DD HH:mm:ss z", + timestamp: FORMAT_NO_TIMESTAMP, // Legacy/deprecated, use timestamp_format below instead ...DefaultLogMessages, }, ping_user: true, + timestamp_format: "YYYY-MM-DD HH:mm:ss z", + include_embed_timestamp: true, }, overrides: [ diff --git a/backend/src/plugins/Logs/types.ts b/backend/src/plugins/Logs/types.ts index 74b43697..759d7581 100644 --- a/backend/src/plugins/Logs/types.ts +++ b/backend/src/plugins/Logs/types.ts @@ -20,6 +20,8 @@ const LogChannel = t.partial({ excluded_message_regexes: t.array(TRegex), excluded_channels: t.array(t.string), format: tNullable(tLogFormats), + timestamp_format: t.string, + include_embed_timestamp: t.boolean, }); export type TLogChannel = t.TypeOf; @@ -31,13 +33,20 @@ export const ConfigSchema = t.type({ format: t.intersection([ tLogFormats, t.type({ - timestamp: t.string, + timestamp: t.string, // Legacy/deprecated }), ]), ping_user: t.boolean, + timestamp_format: t.string, + include_embed_timestamp: t.boolean, }); export type TConfigSchema = t.TypeOf; +// Hacky way of allowing a """null""" default value for config.format.timestamp +// The type cannot be made nullable properly because io-ts's intersection type still considers +// that it has to match the record type of tLogFormats, which includes string. +export const FORMAT_NO_TIMESTAMP = "__NO_TIMESTAMP__"; + export interface LogsPluginType extends BasePluginType { config: TConfigSchema; state: { diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index c6c00d99..1fd4a809 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -1,5 +1,5 @@ import { GuildPluginData } from "knub"; -import { LogsPluginType, TLogFormats } from "../types"; +import { FORMAT_NO_TIMESTAMP, LogsPluginType, TLogChannel, TLogFormats } from "../types"; import { LogType } from "../../../data/LogType"; import { verboseUserMention, @@ -19,14 +19,27 @@ export async function getLogMessage( pluginData: GuildPluginData, type: LogType, data: any, - formats?: TLogFormats, + opts?: Pick, ): Promise { const config = pluginData.config.get(); - const format = (formats && formats[LogType[type]]) || config.format[LogType[type]] || ""; - if (format === "") return; + const format = opts?.format?.[LogType[type]] || config.format[LogType[type]] || ""; + if (format === "" || format == null) return; + + // See comment on FORMAT_NO_TIMESTAMP in types.ts + const timestampFormat = + opts?.timestamp_format ?? + (config.format.timestamp !== FORMAT_NO_TIMESTAMP ? config.format.timestamp : null) ?? + config.timestamp_format; + + const includeEmbedTimestamp = opts?.include_embed_timestamp ?? config.include_embed_timestamp; + + const time = pluginData.getPlugin(TimeAndDatePlugin).inGuildTz(); + const isoTimestamp = time.toISOString(); + const timestamp = timestampFormat ? time.format(timestampFormat) : ""; const values = { ...data, + timestamp, userMention: async inputUserOrMember => { if (!inputUserOrMember) return ""; @@ -73,7 +86,8 @@ export async function getLogMessage( let formatted; try { - formatted = typeof format === "string" ? await renderLogString(format) : renderRecursively(format, renderLogString); + formatted = + typeof format === "string" ? await renderLogString(format) : await renderRecursively(format, renderLogString); } catch (e) { if (e instanceof TemplateParseError) { logger.error(`Error when parsing template:\nError: ${e.message}\nTemplate: ${format}`); @@ -85,15 +99,11 @@ export async function getLogMessage( if (typeof formatted === "string") { formatted = formatted.trim(); - - const timestampFormat = config.format.timestamp; - if (timestampFormat) { - const timestamp = pluginData - .getPlugin(TimeAndDatePlugin) - .inGuildTz() - .format(timestampFormat); + if (timestamp) { formatted = `\`[${timestamp}]\` ${formatted}`; } + } else if (formatted != null && formatted.embed && includeEmbedTimestamp) { + formatted.embed.timestamp = isoTimestamp; } return formatted; diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index 56b36015..6abdedaa 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -62,7 +62,12 @@ export async function log(pluginData: GuildPluginData, type: Log } } - const message = await getLogMessage(pluginData, type, data, opts.format); + const message = await getLogMessage(pluginData, type, data, { + format: opts.format, + include_embed_timestamp: opts.include_embed_timestamp, + timestamp_format: opts.timestamp_format, + }); + if (message) { // For non-string log messages (i.e. embeds) batching or chunking is not possible, so send them immediately if (typeof message !== "string") {