diff --git a/module/tweaks/chatImageLinks.mjs b/module/tweaks/chatImageLinks.mjs index 1e6f708..b75ad9c 100644 --- a/module/tweaks/chatImageLinks.mjs +++ b/module/tweaks/chatImageLinks.mjs @@ -3,8 +3,6 @@ import { __ID__ } from "../consts.mjs"; import { Logger } from "../utils/Logger.mjs"; import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs"; -// const { DialogV2 } = foundry.applications.api; - const key = `chatImageLinks`; const IMAGE_TYPES = [ `png`, @@ -18,6 +16,9 @@ export function chatImageLinks() { status[key] = SettingStatusEnum.Unknown; if (preventTweakRegistration(key)) { return }; + /** @type {number|null} */ + let hookID = null; + // #region Registration Logger.log(`Registering setting: ${key}`); game.settings.register(__ID__, key, { @@ -28,6 +29,13 @@ export function chatImageLinks() { default: true, config: true, requiresReload: true, + onChange: (newValue) => { + if (newValue) { + hookID = Hooks.on(`chatMessage`, chatMessageHandler); + } else if (hookID != null) { + Hooks.off(`chatMessage`, hookID); + }; + }, }); game.settings.register(__ID__, key + `-showPromptAgain`, { @@ -40,76 +48,71 @@ export function chatImageLinks() { // #region Implementation if (game.settings.get(__ID__, key)) { - Logger.log(`setting:${key} | Adding text enricher`); - - // MARK: Enricher - const pattern = new RegExp( - `(? { - Logger.debug(url); - url = url[0].replace(/^image:\/\//, ``); - const secure = `https://${url}`; - const insecure = `http://${url}`; - - if (await isAcceptableImage(secure)) { - const img = document.createElement(`img`); - img.src = secure; - img.alt = secure; - return img; - }; - - if (await isAcceptableImage(insecure)) { - const img = document.createElement(`img`); - img.src = insecure; - img.alt = insecure; - return img; - }; - - return null; - }, - }); - - // MARK: Chat Input - // Hooks.on(`chatMessage`, (chatLog, message, options) => { - // if (!game.settings.get(__ID__, key)) { return }; - - // const match = message.match(pattern); - // if (!match) { return }; - - // DialogV2.wait({ - // rejectClose: false, - // content: game.i18n.localize(`OFT.dialogs.chatImageLinks.didYouKnowImageLink`), - // buttons: [ - // { action: ``, label: `OFT.dialogs.chatImageLinks.convertAndDontShowAgain` }, - // { action: ``, label: `OFT.dialogs.chatImageLinks.justConvert` }, - // { action: ``, label: `OFT.dialogs.chatImageLinks.ignoreAndDontShowAgain` }, - // { action: ``, label: `OFT.dialogs.chatImageLinks.disableEntirely` }, - // ], - // }) - // .then((selected) => { - // chatLog.processMessage(message, options); - // }); - - // return false; - // }); - } + Logger.log(`setting:${key} | Adding chat message listener`); + Hooks.on(`chatMessage`, chatMessageHandler); + }; // #endregion Implementation status[key] = SettingStatusEnum.Registered; }; // #region Helpers +const pattern = new RegExp( + `https?:\\/\\/\\S*\\.(?:${IMAGE_TYPES.join(`|`)})`, + `gi`, +); + +// MARK: Mutate & Resend +const handled = new Set(); +async function mutateAndResendMessage(chatLog, message, options) { + const match = message.match(pattern); + if (!match) { return }; + + const validMatches = new Set(); + + const matches = message.match(pattern); + for (const match of matches) { + if (await isAcceptableImage(match)) { + validMatches.add(match); + }; + }; + + message = message.replaceAll( + pattern, + (url) => { + if (!validMatches.has(url)) { + return url; + }; + return `${url}`; + }, + ); + + handled.add(message); + chatLog.processMessage(message, options); +}; + +// MARK: Chat Message +/** + * Must be synchronous since it is a hook handler, but the mutation + + * resending can be done asynchronously since it doesn't matter how + * long it takes. + */ +function chatMessageHandler(chatLog, message, options) { + if (!game.settings.get(__ID__, key)) { return }; + if (handled.has(message)) { return }; + mutateAndResendMessage(chatLog, message, options); + return false; +}; + +// MARK: isAcceptableImage async function isAcceptableImage(url) { + if (!URL.canParse(url)) { return false }; + try { const response = await fetch(url, { method: `HEAD` }); const contentType = response.headers.get(`Content-Type`); Logger.debug(`Image data:`, { url, contentType }); + let [ superType, subtype ] = contentType.split(`/`); if (superType !== `image`) { return false;