125 lines
3 KiB
JavaScript
125 lines
3 KiB
JavaScript
import { SettingStatusEnum, status } from "../utils/SettingStatus.mjs";
|
|
import { __ID__ } from "../consts.mjs";
|
|
import { Logger } from "../utils/Logger.mjs";
|
|
import { preventTweakRegistration } from "../utils/preRegisterTweak.mjs";
|
|
|
|
const key = `chatImageLinks`;
|
|
const IMAGE_TYPES = [
|
|
`png`,
|
|
`jpg`,
|
|
`jpeg`,
|
|
`webp`,
|
|
`svg`,
|
|
];
|
|
|
|
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, {
|
|
name: `OFT.setting.${key}.name`,
|
|
hint: `OFT.setting.${key}.hint`,
|
|
scope: `user`,
|
|
type: Boolean,
|
|
default: true,
|
|
config: true,
|
|
requiresReload: false,
|
|
onChange: (newValue) => {
|
|
if (newValue) {
|
|
hookID = Hooks.on(`chatMessage`, chatMessageHandler);
|
|
} else if (hookID != null) {
|
|
Hooks.off(`chatMessage`, hookID);
|
|
};
|
|
},
|
|
});
|
|
// #endregion Registration
|
|
|
|
// #region Implementation
|
|
if (game.settings.get(__ID__, key)) {
|
|
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 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 `<img src="${url}" alt="${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 };
|
|
|
|
// Don't re-process the same message
|
|
if (handled.has(message)) { return };
|
|
|
|
// Prevent cancellation for non-matching messages
|
|
const match = message.match(pattern);
|
|
if (!match) { 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;
|
|
};
|
|
if (subtype.includes(`+`)) {
|
|
subtype = subtype.split(`+`, 2).at(0);
|
|
};
|
|
return IMAGE_TYPES.includes(subtype);
|
|
} catch {
|
|
return false;
|
|
};
|
|
};
|
|
// #endregion Helpers
|