From 2a55ff522e8da72fc6bf389105c07a6e2f0f6898 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 13 Dec 2025 01:35:27 -0700 Subject: [PATCH] Add a way to auto-convert images in the chat into the img HTML tag --- module/oft.mjs | 2 + module/settings/chatImageLinks.mjs | 121 +++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 module/settings/chatImageLinks.mjs diff --git a/module/oft.mjs b/module/oft.mjs index a54bfc2..a3b5d5a 100644 --- a/module/oft.mjs +++ b/module/oft.mjs @@ -19,6 +19,7 @@ import { HotbarSettingsMenu } from "./apps/HotbarSettingsMenu.mjs"; // Misc import { __ID__ } from "./consts.mjs"; +import { chatImageLinks } from "./settings/chatImageLinks.mjs"; const { deepFreeze } = foundry.utils; const status = {}; @@ -46,6 +47,7 @@ Hooks.on(`setup`, () => { status.hotbarButtonGap = hotbarButtonGap(); status.repositionHotbar = repositionHotbar(); + status.chatImageLinks = chatImageLinks(); status.chatSidebarBackground = chatSidebarBackground(); status.startSidebarExpanded = startSidebarExpanded(); status.startingSidebarTab = startingSidebarTab(); diff --git a/module/settings/chatImageLinks.mjs b/module/settings/chatImageLinks.mjs new file mode 100644 index 0000000..77e06e2 --- /dev/null +++ b/module/settings/chatImageLinks.mjs @@ -0,0 +1,121 @@ +import { __ID__ } from "../consts.mjs"; +import { Logger } from "../utils/Logger.mjs"; + +const { DialogV2 } = foundry.applications.api; + +const key = `chatImageLinks`; +const IMAGE_TYPES = [ + `png`, + `jpg`, + `jpeg`, + `webp`, + `svg`, +]; + +export function chatImageLinks() { + + // #region Registration + Logger.log(`Registering setting: ${key}`); + game.settings.register(__ID__, key, { + name: `OFT.setting.${key}.name`, + hint: `OFT.setting.${key}.hint`, + scope: `user`, + type: Boolean, + default: true, + config: true, + requiresReload: true, + }); + + game.settings.register(__ID__, key + `-showPromptAgain`, { + scope: `user`, + type: Boolean, + default: true, + config: false, + }); + // #endregion Registration + + // #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; + // }); + } + // #endregion Implementation + + return true; +}; + +// #region Helpers +async function isAcceptableImage(url) { + 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; + }; + if (subtype.includes(`+`)) { + subtype = subtype.split(`+`, 2).at(0); + }; + return IMAGE_TYPES.includes(subtype); + } catch { + return false; + }; +}; +// #endregion Helpers