From 8af6d8545b41e221b5acf9bca0276a4d519b7b0e Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Tue, 27 Feb 2024 15:19:14 +0600 Subject: [PATCH 1/9] Create a proxy for chat to ensure backward compatibility of migrated functions from chat to comm --- core/code/chat.js | 143 ++++++++++++++++++++++++++++++++++++++++------ core/code/comm.js | 9 ++- 2 files changed, 134 insertions(+), 18 deletions(-) diff --git a/core/code/chat.js b/core/code/chat.js index 822bafe13..553bbbcb4 100644 --- a/core/code/chat.js +++ b/core/code/chat.js @@ -6,6 +6,78 @@ var chat = function () {}; window.chat = chat; +// List of functions to track for synchronization between chat and comm +const legacyFunctions = [ + 'genPostData', + 'updateOldNewHash', + 'parseMsgData', + 'writeDataToHash', + 'renderText', + 'getChatPortalName', + 'renderPortal', + 'renderFactionEnt', + 'renderPlayer', + 'renderMarkupEntity', + 'renderMarkup', + 'renderTimeCell', + 'renderNickCell', + 'renderMsgCell', + 'renderMsgRow', + 'renderDivider', + 'renderData', +]; +const newCommApi = [ + '_genPostData', + '_updateOldNewHash', + 'parseMsgData', + '_writeDataToHash', + 'renderText', + 'getChatPortalName', + 'renderPortal', + 'renderFactionEnt', + 'renderPlayer', + 'renderMarkupEntity', + 'renderMarkup', + 'renderTimeCell', + 'renderNickCell', + 'renderMsgCell', + 'renderMsgRow', + 'renderDivider', + 'renderData', +]; + +// Function to map legacy function names to their new names in comm +function mapLegacyFunctionNameToCommApi(functionName) { + const index = legacyFunctions.indexOf(functionName); + return index !== -1 ? newCommApi[index] : functionName; +} + +// Create a proxy for chat to ensure backward compatibility of migrated functions from chat to comm +window.chat = new Proxy(window.chat, { + get(target, prop, receiver) { + if (prop in target) { + // Return the property from chat if it's defined + return target[prop]; + } else if (legacyFunctions.includes(prop)) { + // Map the legacy function name to its new name in comm and return the corresponding function + const commProp = mapLegacyFunctionNameToCommApi(prop); + return window.IITC.comm[commProp]; + } + // Return default value if the property is not found + return Reflect.get(target, prop, receiver); + }, + set(target, prop, value) { + if (legacyFunctions.includes(prop)) { + // Map the legacy function name to its new name in comm and synchronize the function between chat and comm + const commProp = mapLegacyFunctionNameToCommApi(prop); + window.IITC.comm[commProp] = value; + } + // Update or add the property in chat + target[prop] = value; + return true; // Indicates that the assignment was successful + }, +}); + // // common // @@ -472,23 +544,6 @@ chat.setupTabs = function () { chat.renderAlerts = function (oldMsgsWereAdded) { return IITC.comm.renderChannel('allerts', oldMsgsWereAdded); }; - - chat.getChatPortalName = IITC.comm.getChatPortalName; - - /** - * Renders data from the data-hash to the element defined by the given ID. - * - * @function chat.renderData - * @param {Object} data - Chat data to be rendered. - * @param {string} element - ID of the DOM element to render the chat into. - * @param {boolean} likelyWereOldMsgs - Flag indicating if older messages are likely to have been added. - * @param {Array} sortedGuids - Sorted array of GUIDs representing the order of messages. - * @memberof window.chat - * @type {Object} - */ - chat.renderData = function (data, element, likelyWereOldMsgs, sortedGuids) { - return IITC.comm.renderData(data, element, likelyWereOldMsgs, sortedGuids); - }; }; /** @@ -642,4 +697,58 @@ chat.setupPosting = function () { }); }; +/** + * Legacy function for rendering chat messages. Used for backward compatibility with plugins. + * + * @deprecated + * @function window.chat.renderMsg + * @param {string} msg - The chat message. + * @param {string} nick - The nickname of the player who sent the message. + * @param {number} time - The timestamp of the message. + * @param {string} team - The team of the player who sent the message. + * @param {boolean} msgToPlayer - Flag indicating if the message is directed to the player. + * @param {boolean} systemNarrowcast - Flag indicating if the message is a system narrowcast. + * @returns {string} The HTML string representing a chat message row. + */ +chat.renderMsg = function (msg, nick, time, team, msgToPlayer, systemNarrowcast) { + // Imitating data usually derived from processing raw chat data + var fakeData = { + guid: 'legacyguid-' + Math.random(), + time: time, + public: !systemNarrowcast, + secure: systemNarrowcast, + alert: msgToPlayer, + msgToPlayer: msgToPlayer, + type: systemNarrowcast ? 'SYSTEM_NARROWCAST' : 'PLAYER_GENERATED', + narrowcast: systemNarrowcast, + auto: false, // Assuming the message is player-generated if it's not a system broadcast + team: team, + player: { + name: nick, + team: team, + }, + markup: [ + ['TEXT', { plain: msg }], // A simple message with no special markup + ], + }; + + // Use existing IITC functions to render a chat message row + return IITC.comm.renderMsgRow(fakeData); +}; + +/** + * Legacy function for converts a chat tab name to its corresponding COMM channel name. + * Used for backward compatibility with plugins. + * + * @deprecated + * @function window.chat.tabToChannel + * @param {string} tab - The name of the chat tab. + * @returns {string} The corresponding channel name ('faction', 'alerts', or 'all'). + */ +chat.tabToChannel = function (tab) { + if (tab === 'faction') return 'faction'; + if (tab === 'alerts') return 'alerts'; + return 'all'; +}; + /* global log, PLAYER, L, IITC, app */ diff --git a/core/code/comm.js b/core/code/comm.js index f5219143c..92b98d097 100644 --- a/core/code/comm.js +++ b/core/code/comm.js @@ -239,12 +239,16 @@ var _oldBBox = null; * @private * @param {string} channel - The chat channel. * @param {boolean} getOlderMsgs - Flag to determine if older messages are being requested. + * @param args=undefined - Used for backward compatibility when calling a function with three arguments. * @returns {Object} The generated post data. */ -function _genPostData(channel, getOlderMsgs) { +function _genPostData(channel, getOlderMsgs, ...args) { if (typeof channel !== 'string') { throw new Error('API changed: isFaction flag now a channel string - all, faction, alerts'); } + if (args.length === 1) { + getOlderMsgs = args[0]; + } var b = window.clampLatLngBounds(map.getBounds()); @@ -776,6 +780,9 @@ IITC.comm = { renderChannel, renderData, _channelsData, + _genPostData, + _updateOldNewHash, + _writeDataToHash, }; /* global log, map, chat, IITC */ From 6eb9eb050a40a92c61f3f925aa6a218d71db5f9e Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Tue, 5 Mar 2024 11:39:45 +0500 Subject: [PATCH 2/9] Added a list of transformations for IITC.comm.getChatPortalName (IITC.comm.portalNameTransformations) so that you can extend transformation rules from plugins without having to rewrite the function --- core/code/comm.js | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/core/code/comm.js b/core/code/comm.js index 92b98d097..e319bc2d2 100644 --- a/core/code/comm.js +++ b/core/code/comm.js @@ -434,20 +434,46 @@ function renderText(text) { return content.html().autoLink(); } +/** + * List of transformations for portal names used in chat. + * Each transformation function takes the portal markup object and returns a transformed name. + * If a transformation does not apply, the original name is returned. + * + * @const IITC.comm.portalNameTransformations + * @example + * // Adding a transformation that appends the portal location to its name + * portalNameTransformations.push((markup) => { + * const latlng = `${markup.latE6 / 1E6},${markup.lngE6 / 1E6}`; // Convert E6 format to decimal + * return `[${latlng}] ${markup.name}`; + * }); + */ +const portalNameTransformations = [ + // Transformation for 'US Post Office' + (markup) => { + if (markup.name === 'US Post Office') { + const address = markup.address.split(','); + return 'USPS: ' + address[0]; + } + return markup.name; + }, +]; + /** * Overrides portal names used repeatedly in chat, such as 'US Post Office', with more specific names. + * Applies a series of transformations to the portal name based on the portal markup. * * @function IITC.comm.getChatPortalName * @param {Object} markup - An object containing portal markup, including the name and address. * @returns {string} The processed portal name. */ function getChatPortalName(markup) { - var name = markup.name; - if (name === 'US Post Office') { - var address = markup.address.split(','); - name = 'USPS: ' + address[0]; - } - return name; + // Use reduce to apply each transformation to the data + const transformedData = portalNameTransformations.reduce((initialMarkup, transform) => { + const updatedName = transform(initialMarkup); + return {...initialMarkup, name: updatedName}; + }, markup); + + return transformedData.name; } /** @@ -761,6 +787,8 @@ IITC.comm = { channels: _channels, sendChatMessage, parseMsgData, + // List of transformations + portalNameTransformations, // Render primitive, may be override renderMsgRow, renderDivider, From ac231eeaeebe5b988ded9a1dddc2dcb8236dafba Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Tue, 5 Mar 2024 11:41:40 +0500 Subject: [PATCH 3/9] Added a transformation list for IITC.comm.transformMessage (IITC.comm.messageTransformFunctions) so that plugin developers can add new message transformation rules or generate entirely new message markup without having to rewrite the function --- core/code/comm.js | 92 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/core/code/comm.js b/core/code/comm.js index e319bc2d2..746b76254 100644 --- a/core/code/comm.js +++ b/core/code/comm.js @@ -591,42 +591,77 @@ function renderMarkup(markup) { } /** - * Transforms a the markup array into an older, more straightforward format for easier understanding. + * List of transformations to be applied to the message data. + * Each transformation function takes the full message data object and returns the transformed markup. + * The default transformations aim to convert the message markup into an older, more straightforward format, + * facilitating easier understanding and backward compatibility with plugins expecting the older message format. * - * May be used to build an entirely new markup to be rendered without altering the original one. - * - * @function IITC.comm.transformMessage - * @param {Object} data - The data for the message, including time, player, and message content. - * @returns {Array} The transformed markup array with a simplified structure. + * @const IITC.comm.messageTransformFunctions + * @example + * // Adding a new transformation function to the array + * // This new function adds a "new" prefix to the player's plain text if the player is from the RESISTANCE team + * messageTransformFunctions.push((data) => { + * const markup = data.markup; + * if (markup.length > 2 && markup[0][0] === 'PLAYER' && markup[0][1].team === 'RESISTANCE') { + * markup[1][1].plain = 'new ' + markup[1][1].plain; + * } + * return markup; + * }); */ -function transformMessage(data) { - // Make a copy of the markup array to avoid modifying the original input - let newMarkup = JSON.parse(JSON.stringify(data.markup)); - - // Collapse + "Link"/"Field". Example: "Agent destroyed the Link ..." - if (newMarkup.length > 4) { - if (newMarkup[3][0] === 'FACTION' && newMarkup[4][0] === 'TEXT' && (newMarkup[4][1].plain === ' Link ' || newMarkup[4][1].plain === ' Control Field @')) { - newMarkup[4][1].team = newMarkup[3][1].team; - newMarkup.splice(3, 1); +const messageTransformFunctions = [ + // Collapse + "Link"/"Field". + (data) => { + const markup = data.markup; + if ( + markup.length > 4 && + markup[3][0] === 'FACTION' && + markup[4][0] === 'TEXT' && + (markup[4][1].plain === ' Link ' || markup[4][1].plain === ' Control Field @') + ) { + markup[4][1].team = markup[3][1].team; + markup.splice(3, 1); } - } - + return markup; + }, // Skip "Agent " at the beginning - if (newMarkup.length > 1) { - if (newMarkup[0][0] === 'TEXT' && newMarkup[0][1].plain === 'Agent ' && newMarkup[1][0] === 'PLAYER') { - newMarkup.splice(0, 2); + (data) => { + const markup = data.markup; + if (markup.length > 1 && markup[0][0] === 'TEXT' && markup[0][1].plain === 'Agent ' && markup[1][0] === 'PLAYER') { + markup.splice(0, 2); } - } - + return markup; + }, // Skip " agent " at the beginning - if (newMarkup.length > 2) { - if (newMarkup[0][0] === 'FACTION' && newMarkup[1][0] === 'TEXT' && newMarkup[1][1].plain === ' agent ' && newMarkup[2][0] === 'PLAYER') { - newMarkup.splice(0, 3); + (data) => { + const markup = data.markup; + if (markup.length > 2 && markup[0][0] === 'FACTION' && markup[1][0] === 'TEXT' && markup[1][1].plain === ' agent ' && markup[2][0] === 'PLAYER') { + markup.splice(0, 3); } - } + return markup; + }, +]; - return newMarkup; -} +/** + * Applies transformations to the markup array based on the transformations defined in + * the {@link IITC.comm.messageTransformFunctions} array. + * Assumes all transformations return a new markup array. + * May be used to build an entirely new markup to be rendered without altering the original one. + * + * @function IITC.comm.transformMessage + * @param {Object} data - The data for the message, including time, player, and message content. + * @returns {Object} The transformed markup array. + */ +const transformMessage = (data) => { + const initialData = JSON.parse(JSON.stringify(data)); + + // Use reduce to apply each transformation to the data + const transformedData = messageTransformFunctions.reduce((data, transform) => { + const updatedMarkup = transform(data); + return {...data, markup: updatedMarkup}; + }, initialData); + + return transformedData.markup; +}; /** * Renders a cell in the chat table to display the time a message was sent. @@ -789,6 +824,7 @@ IITC.comm = { parseMsgData, // List of transformations portalNameTransformations, + messageTransformFunctions, // Render primitive, may be override renderMsgRow, renderDivider, From b350d3847d79c108d7e90d5b7647fa1b5d38469c Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Tue, 5 Mar 2024 21:09:18 +0500 Subject: [PATCH 4/9] Added declarative COMM message filter (IITC.comm.declarativeMessageFilter), implemented testing of the filter --- core/code/comm.js | 1 + core/code/comm_declarative_message_filter.js | 150 ++++++++++++++ package.json | 6 +- test/comm_declarative_message_filter.spec.js | 200 +++++++++++++++++++ 4 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 core/code/comm_declarative_message_filter.js create mode 100644 test/comm_declarative_message_filter.spec.js diff --git a/core/code/comm.js b/core/code/comm.js index 746b76254..a8c06269b 100644 --- a/core/code/comm.js +++ b/core/code/comm.js @@ -789,6 +789,7 @@ function renderData(data, element, likelyWereOldMsgs, sortedGuids) { var prevTime = null; vals.forEach(function (guid) { var msg = data[guid]; + if (IITC.comm.declarativeMessageFilter.filterMessage(msg[4])) return; var nextTime = new Date(msg[0]).toLocaleDateString(); if (prevTime && prevTime !== nextTime) { msgs += IITC.comm.renderDivider(nextTime); diff --git a/core/code/comm_declarative_message_filter.js b/core/code/comm_declarative_message_filter.js new file mode 100644 index 000000000..41433b699 --- /dev/null +++ b/core/code/comm_declarative_message_filter.js @@ -0,0 +1,150 @@ +/* global IITC */ + +/** + * Declarative message filter for COMM API + * + * @memberof IITC.comm + * @namespace declarativeMessageFilter + */ + +IITC.comm.declarativeMessageFilter = { + _rules: {}, + + /** + * Adds a new filtering rule with a given ID. + * + * @param {string} id The ID of the rule to add. + * @param {Object} rule The rule to add. + * + * @example + * // Hide all messages from Resistance team + * IITC.comm.declarativeMessageFilter.addRule({ + * id: "hideResistanceTeam1", + * conditions: [ + * { field: "player.team", value: "Resistance" }, + * ] + * }); + * + * @example + * // Hide all messages except those from the Resistance team using the inverted rule + * IITC.comm.declarativeMessageFilter.addRule({ + * id: "hideExceptResistanceTeam", + * conditions: [ + * { field: "player.team", value: "Resistance", invert: true }, + * ] + * }); + * + * @example + * // Hide messages that look like spam + * IITC.comm.declarativeMessageFilter.addRule({ + * id: "hideSpam", + * conditions: [ + * { field: "markup[4][1].plain", condition: /ingress-(shop|store)|(store|shop)-ingress/i }, + * ] + * }); + */ + addRule: (id, rule) => { + if (IITC.comm.declarativeMessageFilter._rules[id]) { + console.warn(`Rule with ID '${id}' already exists. Overwriting.`); + } + IITC.comm.declarativeMessageFilter._rules[id] = rule; + }, + + /** + * Removes a filtering rule by its ID. + * + * @param {string} id The ID of the rule to remove. + */ + removeRule: (id) => { + if (IITC.comm.declarativeMessageFilter._rules[id]) { + delete IITC.comm.declarativeMessageFilter._rules[id]; + } else { + console.error(`No rule found with ID '${id}'.`); + } + }, + + /** + * Gets a rule by its ID. + * + * @param {string} id The ID of the rule to get. + * @returns {Object|null} The rule object, or null if not found. + */ + getRuleById: (id) => { + return IITC.comm.declarativeMessageFilter._rules[id] || null; + }, + + /** + * Gets all current filtering rules. + * + * @returns {Object} The current set of filtering rules. + */ + getAllRules: () => { + return IITC.comm.declarativeMessageFilter._rules; + }, + + /** + * Extracts the value from the message object by a given path. + * + * @param {Object} object The message object. + * @param {String} path Path to the property in dot notation. + * @returns {*} The value of the property at the specified path or undefined if the path is not valid. + */ + getMessageValueByPath: (object, path) => { + const parts = path.replace(/\[(\w+)]/g, '.$1').split('.'); + let current = object; + + for (const part of parts) { + if (part in current) { + current = current[part]; + } else { + return undefined; // Path is not valid + } + } + return current; + }, + + /** + * Checks if the message matches a single rule. + * + * @param {Object} message The message to check. + * @param {Object} rule The rule to match against. + * @returns {boolean} True if the message matches the rule, false otherwise. + */ + matchesRule: (message, rule) => { + return rule.conditions.every((condition) => { + const messageValue = IITC.comm.declarativeMessageFilter.getMessageValueByPath(message, condition.field); + let result; + + if ('value' in condition) { + result = messageValue === condition.value; + } else if ('pattern' in condition) { + const regex = new RegExp(condition.pattern); + result = regex.test(messageValue); + } else { + return false; // If the condition does not contain 'value' or 'pattern', we consider that the message does not match the rule + } + + // Invert the result if the condition is inverted + if (condition.invert) { + return !result; + } + return result; + }); + }, + + /** + * Checks if a message matches any of the current filtering rules. + * + * @param {Object} message The message to check. + * @returns {boolean} True if the message matches any rule, false otherwise. + */ + filterMessage: (message) => { + const rules = IITC.comm.declarativeMessageFilter.getAllRules(); + for (const ruleId in rules) { + if (IITC.comm.declarativeMessageFilter.matchesRule(message, rules[ruleId])) { + return true; + } + } + return false; + }, +}; diff --git a/package.json b/package.json index 7df5fde0a..b641bdd62 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,20 @@ "repository": "https://github.com/IITC-CE/ingress-intel-total-conversion.git", "license": "ISC", "private": true, + "type": "module", "devDependencies": { + "chai": "^4.3.6", "eslint": "^8.20.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", + "mocha": "^9.2.2", "prettier": "^2.7.1" }, "scripts": { "build": "npm run build:local", "build:local": "./build.py local", "build:mobile": "./build.py mobile", - "fileserver": "./web_server_local.py local" + "fileserver": "./web_server_local.py local", + "test": "mocha --exit" } } diff --git a/test/comm_declarative_message_filter.spec.js b/test/comm_declarative_message_filter.spec.js new file mode 100644 index 000000000..a0b4c8094 --- /dev/null +++ b/test/comm_declarative_message_filter.spec.js @@ -0,0 +1,200 @@ +import { describe, it, before } from 'mocha'; +import { expect } from 'chai'; + +globalThis.IITC = { comm: {} }; +import('../core/code/comm_declarative_message_filter.js'); + +// Define test messages +const testMessages = [ + { + "guid":"3d.d", + "time":1709, + "public":false, + "secure":true, + "alert":false, + "msgToPlayer":false, + "type":"SYSTEM_BROADCAST", + "narrowcast":false, + "auto":true, + "team":2, + "player": { + "name":"Spedd", + "team":2 + }, + "markup":[ + ["TEXT",{"plain":"Drone returned to Agent by "}], + ["PLAYER",{"plain":"Spedd","team":"ENLIGHTENED"}] + ] + }, + { + "guid":"40.d", + "time":1709, + "public":false, + "secure":true, + "alert":false, + "msgToPlayer":false, + "type":"PLAYER_GENERATED", + "narrowcast":false, + "auto":false, + "team":2, + "player": { + "name":"43Bad", + "team":2 + }, + "markup":[ + ["SECURE",{"plain":"[secure] "}], + ["SENDER",{"plain":"43Bad: ","team":"ENLIGHTENED"}], + ["TEXT",{"plain":""}], + ["AT_PLAYER",{"plain":"@RockDeckard","team":"ENLIGHTENED"}], + ["TEXT",{"plain":": HI!"}] + ] + }, + { + "guid":"40.d", + "time":1709, + "public":true, + "secure":false, + "alert":false, + "msgToPlayer":false, + "type":"SYSTEM_BROADCAST", + "narrowcast":false, + "auto":true, + "team":0, + "player": { + "name":"b3387", + "team":2 + }, + "markup":[ + ["TEXT",{"plain":"Agent "}], + ["PLAYER",{"plain":"b3387","team":"ENLIGHTENED"}], + ["TEXT",{"plain":" destroyed the "}], + ["FACTION",{"team":"NEUTRAL","plain":"Neutral"}], + ["TEXT",{"plain":" Link "}], + ["PORTAL",{"plain":"Turner Ave. (London N15 5DG, UK)","name":"Turner Ave","address":"London N15 5DG, UK","latE6":5000,"lngE6":-800,"team":"NEUTRAL"}], + ["TEXT",{"plain":" to "}], + ["PORTAL",{"plain":"Notice board (London N15 5DG, UK)","name":"Notice board","address":"London N15 5DG, UK","latE6":5001,"lngE6":-801,"team":"NEUTRAL"}] + ] + }, + { + "guid":"ae.d", + "time":1709, + "public":true, + "secure":false, + "alert":false, + "msgToPlayer":false, + "type":"SYSTEM_BROADCAST", + "narrowcast":false, + "auto":true, + "team":1, + "player": { + "name":"Q77n", + "team":1 + }, + "markup":[ + ["PLAYER",{"plain":"Q77n","team":"RESISTANCE"}], + ["TEXT",{"plain":" captured "}], + ["PORTAL",{"plain":"Bridge (UK)","name":"Bridge","address":"UK","latE6":6000,"lngE6":-4000,"team":"RESISTANCE"}] + ] + }, +]; + +describe('IITC Comm Declarative Message Filter', () => { + beforeEach(() => { + // Reset rules before each test + IITC.comm.declarativeMessageFilter._rules = {}; + }); + + it('should add a new rule successfully', () => { + const ruleId = 'testRule'; + const rule = { + conditions: [{ field: 'player.team', value: 1 }], + }; + + IITC.comm.declarativeMessageFilter.addRule(ruleId, rule); + + const addedRule = IITC.comm.declarativeMessageFilter.getRuleById(ruleId); + expect(addedRule).to.deep.equal(rule); + }); + + it('should remove an existing rule by ID', () => { + const ruleId = 'testRule'; + IITC.comm.declarativeMessageFilter.addRule(ruleId, { conditions: [] }); + IITC.comm.declarativeMessageFilter.removeRule(ruleId); + + const ruleAfterRemoval = IITC.comm.declarativeMessageFilter.getRuleById(ruleId); + expect(ruleAfterRemoval).to.be.null; + }); + + it('should return all current rules', () => { + const rules = { + rule1: { conditions: [{ field: 'player.team', value: 1 }] }, + rule2: { conditions: [{ field: 'player.team', value: 2 }] }, + }; + + for (const id in rules) { + IITC.comm.declarativeMessageFilter.addRule(id, rules[id]); + } + + const allRules = IITC.comm.declarativeMessageFilter.getAllRules(); + expect(allRules).to.deep.equal(rules); + }); + + // Example: Test matching a rule with a direct comparison + it('should correctly filter messages based on a direct comparison condition', () => { + const ruleId = 'directComparison'; + const rule = { + conditions: [{ field: 'player.name', value: '43Bad' }], + }; + + IITC.comm.declarativeMessageFilter.addRule(ruleId, rule); + + const shouldNotMatch1 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[0]); + const shouldMatch = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[1]); + const shouldNotMatch2 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[2]); + const shouldNotMatch3 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[3]); + + expect(shouldMatch).to.be.true; + expect(shouldNotMatch1).to.be.false; + expect(shouldNotMatch2).to.be.false; + expect(shouldNotMatch3).to.be.false; + }); + + // Example: Test matching a rule with a regex pattern + it('should correctly filter messages based on a regex pattern condition', () => { + const ruleId = 'regexPattern'; + const rule = { + conditions: [{ field: 'markup[5][1].plain', pattern: /UK\)$/i }], + }; + + IITC.comm.declarativeMessageFilter.addRule(ruleId, rule); + + const shouldNotMatch1 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[0]); + const shouldNotMatch2 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[1]); + const shouldMatch = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[2]); + const shouldNotMatch3 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[3]); + + expect(shouldMatch).to.be.true; + expect(shouldNotMatch1).to.be.false; + expect(shouldNotMatch2).to.be.false; + expect(shouldNotMatch3).to.be.false; + }); + + it('should correctly filter messages based on an inverted condition', () => { + const ruleId = 'invertCondition'; + const rule = { + conditions: [{ field: 'player.team', value: 1, invert: true }], + }; + + IITC.comm.declarativeMessageFilter.addRule(ruleId, rule); + + const shouldMatch1 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[0]); + const shouldMatch2 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[1]); + const shouldMatch3 = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[2]); + const shouldNotMatch = IITC.comm.declarativeMessageFilter.filterMessage(testMessages[3]); // This message is from Resistance, so it should not match when inverted + + expect(shouldMatch1).to.be.true; + expect(shouldMatch2).to.be.true; + expect(shouldMatch3).to.be.true; + expect(shouldNotMatch).to.be.false; + }); +}); From e2e1936b1249ae9739a06a39587c3fe2fdbd6270 Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Sun, 10 Mar 2024 21:22:25 +0500 Subject: [PATCH 5/9] HTML code for forming messages has been moved to string templates --- core/code/comm.js | 95 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/core/code/comm.js b/core/code/comm.js index a8c06269b..9cd39358a 100644 --- a/core/code/comm.js +++ b/core/code/comm.js @@ -7,6 +7,7 @@ /** * @type {chat.ChannelDescription[]} + * @memberof IITC.comm */ var _channels = [ { @@ -46,6 +47,7 @@ var _channels = [ * Holds data related to each intel channel. * * @type {Object} + * @memberof IITC.comm */ var _channelsData = {}; @@ -67,6 +69,43 @@ function _initChannelData(id) { delete _channelsData[id].newestGUID; } +/** + * Template of portal link in comm. + * @type {String} + * @memberof IITC.comm + */ +let portalTemplate = '{{ portal_name }}'; +/** + * Template for time cell. + * @type {String} + * @memberof IITC.comm + */ +let timeCellTemplate = ''; +/** + * Template for player's nickname cell. + * @type {String} + * @memberof IITC.comm + */ +let nickCellTemplate = '<{{ nick }}>'; +/** + * Template for chat message text cell. + * @type {String} + * @memberof IITC.comm + */ +let msgCellTemplate = '{{ msg }}'; +/** + * Template for message row, includes cells for time, player nickname and message text. + * @type {String} + * @memberof IITC.comm + */ +let msgRowTemplate = '{{ time_cell }}{{ nick_cell }}{{ msg_cell }}'; +/** + * Template for message divider. + * @type {String} + * @memberof IITC.comm + */ +let dividerTemplate = '
{{ text }}
'; + /** * Updates the oldest and newest message timestamps and GUIDs in the chat storage. * @@ -484,11 +523,17 @@ function getChatPortalName(markup) { * @returns {string} HTML string of the portal link. */ function renderPortal(portal) { - var lat = portal.latE6 / 1e6, - lng = portal.lngE6 / 1e6; - var perma = window.makePermalink([lat, lng]); - var js = 'window.selectPortalByLatLng(' + lat + ', ' + lng + ');return false'; - return '' + IITC.comm.getChatPortalName(portal) + ''; + const lat = portal.latE6 / 1e6; + const lng = portal.lngE6 / 1e6; + const permalink = window.makePermalink([lat, lng]); + const portalName = IITC.comm.getChatPortalName(portal); + + return IITC.comm.portalTemplate + .replace("{{ lat }}", lat.toString()) + .replace("{{ lng }}", lng.toString()) + .replace("{{ title }}", portal.address) + .replace("{{ url }}", permalink) + .replace("{{ portal_name }}", portalName); } /** @@ -668,16 +713,22 @@ const transformMessage = (data) => { * Formats the time and adds it to a