Move message parsing to formatting file.

This commit is contained in:
armored-dragon 2024-12-20 05:32:17 -06:00
parent ae56632c64
commit 0d6af1e90c
No known key found for this signature in database
GPG key ID: C7207ACC3382AD8B
2 changed files with 105 additions and 107 deletions

View file

@ -109,7 +109,7 @@
if (message.channel == "local" && isTooFar(message.position)) return; // If message is local, and if player is too far away from location, do nothing. if (message.channel == "local" && isTooFar(message.position)) return; // If message is local, and if player is too far away from location, do nothing.
let formattedMessagePacket = { ...message }; let formattedMessagePacket = { ...message };
formattedMessagePacket.message = await _parseMessage(message.message) formattedMessagePacket.message = await formatting.parseMessage(message.message)
_emitEvent({ type: "show_message", ...formattedMessagePacket }); // Update qml view of to new message. _emitEvent({ type: "show_message", ...formattedMessagePacket }); // Update qml view of to new message.
_notificationCoreMessage(message.displayName, message.message) // Show a new message on screen. _notificationCoreMessage(message.displayName, message.message) // Show a new message on screen.
@ -219,7 +219,7 @@
// Format notification message // Format notification message
let formattedMessagePacket = {...message}; let formattedMessagePacket = {...message};
formattedMessagePacket.message = await _parseMessage(message.message); formattedMessagePacket.message = await formatting.parseMessage(message.message);
_emitEvent({ type: "notification", ...formattedMessagePacket }); _emitEvent({ type: "notification", ...formattedMessagePacket });
}, 1500); }, 1500);
@ -230,15 +230,15 @@
if (messageHistory) { if (messageHistory) {
// Load message history // Load message history
for (message of messageHistory) { for (message of messageHistory) {
messagePacket = { ...message }; // Create new variable messagePacket = { ...message }; // Create new variable
messagePacket = formatting.addTimeAndDateStringToPacket(messagePacket); // Add timestamp messagePacket = formatting.addTimeAndDateStringToPacket(messagePacket); // Add timestamp
messagePacket.message = await _parseMessage(messagePacket.message); // Parse the message for the UI messagePacket.message = await formatting.parseMessage(messagePacket.message); // Parse the message for the UI
_emitEvent({ type: "show_message", ...messagePacket }); // Send message to UI _emitEvent({ type: "show_message", ...messagePacket }); // Send message to UI
} }
} }
_emitEvent({ type: "initial_settings", settings: settings }); // Send current settings to the app _emitEvent({ type: "initial_settings", settings: settings }); // Send current settings to the app
} }
function _saveSettings() { function _saveSettings() {
console.log("Saving config"); console.log("Saving config");
@ -250,85 +250,6 @@
JSON.stringify({ sender: displayName, text: message }) JSON.stringify({ sender: displayName, text: message })
); );
} }
async function _parseMessage(message){
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
const mentionRegex = /@(\w+)/; // FIXME: Remove - devcode
const overteLocationRegex = /hifi:\/\/[a-zA-Z0-9_-]+\/[-+]?\d*\.?\d+,[+-]?\d*\.?\d+,[+-]?\d*\.?\d+\/[-+]?\d*\.?\d+,[+-]?\d*\.?\d+,[+-]?\d*\.?\d+,[+-]?\d*\.?\d+/;
let runningMessage = message;
let messageArray = [];
const regexPatterns = [
{ type: "url", regex: urlRegex },
{ type: "mention", regex: mentionRegex }, // FIXME: Remove - devcode
{ type: "overteLocation", regex: overteLocationRegex }
]
// Here is a link https://www.example.com, #hashtag, and @mention. Just for some spice here is another https://exampletwo.com
while (true) {
let firstMatch = _findFirstMatch();
if (firstMatch == null) {
// Format any remaining text as a basic 'text' type.
messageArray.push({type: 'text', value: runningMessage});
// Append a final 'fill width' to the message text.
messageArray.push({type: 'messageEnd'});
break;
}
_formatMessage(firstMatch);
}
for (dataChunk of messageArray){
if (dataChunk.type == 'url'){
let url = dataChunk.value;
const res = await fetch(url, {method: 'GET'}); // TODO: Replace with 'HEAD' method. https://github.com/overte-org/overte/issues/1273
const contentType = res.getResponseHeader("content-type");
// TODO: Add support for other media types
if (contentType.startsWith('image/')) {
messageArray.push({type: 'imageEmbed', value: url});
}
}
}
return messageArray;
function _formatMessage(firstMatch){
let indexOfFirstMatch = firstMatch[0];
let regex = regexPatterns[firstMatch[1]].regex;
let foundMatch = runningMessage.match(regex)[0];
messageArray.push({type: 'text', value: runningMessage.substring(0, indexOfFirstMatch)});
messageArray.push({type: regexPatterns[firstMatch[1]].type, value: runningMessage.substring(indexOfFirstMatch, indexOfFirstMatch + foundMatch.length)});
runningMessage = runningMessage.substring(indexOfFirstMatch + foundMatch.length); // Remove the part of the message we have worked with
}
function _findFirstMatch(){
let indexOfFirstMatch = Infinity;
let indexOfRegexPattern = Infinity;
for (let i = 0; regexPatterns.length > i; i++){
let indexOfMatch = runningMessage.search(regexPatterns[i].regex);
if (indexOfMatch == -1) continue; // No match found
if (indexOfMatch < indexOfFirstMatch) {
indexOfFirstMatch = indexOfMatch;
indexOfRegexPattern = i;
}
}
if (indexOfFirstMatch !== Infinity) return [indexOfFirstMatch, indexOfRegexPattern]; // If there was a found match
return null; // No found match
}
}
/** /**
* Emit a packet to the HTML front end. Easy communication! * Emit a packet to the HTML front end. Easy communication!
* @param {Object} packet - The Object packet to emit to the HTML * @param {Object} packet - The Object packet to emit to the HTML
@ -338,27 +259,6 @@
chatOverlayWindow.sendToQml(packet); chatOverlayWindow.sendToQml(packet);
} }
function fetch(url, options = {method: "GET"}) {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState === req.DONE) {
if (req.status === 200) {
console.log("Content type:", req.getResponseHeader("content-type"));
resolve(req);
} else {
console.log("Error", req.status, req.statusText);
reject();
}
}
};
req.open(options.method, url);
req.send();
});
}
})(); })();

View file

@ -38,6 +38,82 @@ const formatting = {
}; };
return newPacket; return newPacket;
}, },
parseMessage: async function(message) {
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
const overteLocationRegex = /hifi:\/\/[a-zA-Z0-9_-]+\/[-+]?\d*\.?\d+,[+-]?\d*\.?\d+,[+-]?\d*\.?\d+\/[-+]?\d*\.?\d+,[+-]?\d*\.?\d+,[+-]?\d*\.?\d+,[+-]?\d*\.?\d+/;
let runningMessage = message; // The remaining message that will be parsed
let messageArray = []; // An array of messages that are split up by the formatting functions
const regexPatterns = [
{ type: "url", regex: urlRegex },
{ type: "overteLocation", regex: overteLocationRegex }
]
while (true) {
let firstMatch = _findFirstMatch();
if (firstMatch == null) {
// If there is no more text to parse, break out of the loop and return the message array.
// Format any remaining text as a basic 'text' type.
messageArray.push({type: 'text', value: runningMessage});
// Append a final 'fill width' to the message text.
messageArray.push({type: 'messageEnd'});
break;
}
_formatMessage(firstMatch);
}
// Embed images in the message array.
for (dataChunk of messageArray){
if (dataChunk.type == 'url'){
let url = dataChunk.value;
const res = await formatting.helpers.fetch(url, {method: 'GET'}); // TODO: Replace with 'HEAD' method. https://github.com/overte-org/overte/issues/1273
const contentType = res.getResponseHeader("content-type");
// TODO: Add support for other media types
if (contentType.startsWith('image/')) {
messageArray.push({type: 'imageEmbed', value: url});
}
}
}
return messageArray;
function _formatMessage(firstMatch){
let indexOfFirstMatch = firstMatch[0];
let regex = regexPatterns[firstMatch[1]].regex;
let foundMatch = runningMessage.match(regex)[0];
messageArray.push({type: 'text', value: runningMessage.substring(0, indexOfFirstMatch)});
messageArray.push({type: regexPatterns[firstMatch[1]].type, value: runningMessage.substring(indexOfFirstMatch, indexOfFirstMatch + foundMatch.length)});
runningMessage = runningMessage.substring(indexOfFirstMatch + foundMatch.length); // Remove the part of the message we have worked with
}
function _findFirstMatch(){
let indexOfFirstMatch = Infinity;
let indexOfRegexPattern = Infinity;
for (let i = 0; regexPatterns.length > i; i++){
let indexOfMatch = runningMessage.search(regexPatterns[i].regex);
if (indexOfMatch == -1) continue; // No match found
if (indexOfMatch < indexOfFirstMatch) {
indexOfFirstMatch = indexOfMatch;
indexOfRegexPattern = i;
}
}
if (indexOfFirstMatch !== Infinity) return [indexOfFirstMatch, indexOfRegexPattern]; // If there was a found match
return null; // No found match
}
},
helpers: { helpers: {
// Small functions that are used often in the other functions. // Small functions that are used often in the other functions.
@ -59,6 +135,28 @@ const formatting = {
}, },
getTimestamp: function(){ getTimestamp: function(){
return Date.now(); return Date.now();
},
fetch: function (url, options = {method: "GET"}) {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState === req.DONE) {
if (req.status === 200) {
console.log("Content type:", req.getResponseHeader("content-type"));
resolve(req);
} else {
console.log("Error", req.status, req.statusText);
reject();
}
}
};
req.open(options.method, url);
req.send();
});
} }
} }
} }