Add a way to auto-convert images in the chat into the img HTML tag

This commit is contained in:
Oliver 2025-12-13 01:35:27 -07:00
parent 18021a32e4
commit 2a55ff522e
2 changed files with 123 additions and 0 deletions

View file

@ -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();

View file

@ -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(
`(?<!=|")(image:\\/\\/.*\\.(?:${IMAGE_TYPES.join(`|`)}))`,
`gi`,
);
CONFIG.TextEditor.enrichers.push({
pattern,
replaceParent: true,
enricher: async (url) => {
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