diff --git a/package.json b/package.json index ae6619cc..e2cba7dc 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,13 @@ "js-levenshtein": "^1.1.6", "reconnecting-websocket": "^4.4.0", "sortablejs": "^1.14.0", - "spark-md5": "^3.0.2" + "spark-md5": "^3.0.2", + "unescape-js": "^1.1.4" }, "pnpm": { "overrides": { "ansi-regex@>2.1.1 <5.0.1": ">=5.0.1", + "chalk@<4": ">=4 <5", "glob-parent@<5.1.2": ">=5.1.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cd53316..1f72a80c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,12 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + overrides: ansi-regex@>2.1.1 <5.0.1: '>=5.0.1' + chalk@<4: '>=4 <5' glob-parent@<5.1.2: '>=5.1.2' dependencies: @@ -29,6 +34,9 @@ dependencies: spark-md5: specifier: ^3.0.2 version: 3.0.2 + unescape-js: + specifier: ^1.1.4 + version: 1.1.4 devDependencies: copy-webpack-plugin: @@ -893,11 +901,6 @@ packages: hasBin: true dev: true - /ansi-regex@2.1.1: - resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} - engines: {node: '>=0.10.0'} - dev: true - /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -908,11 +911,6 @@ packages: engines: {node: '>=12'} dev: true - /ansi-styles@2.2.1: - resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} - engines: {node: '>=0.10.0'} - dev: true - /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1030,7 +1028,7 @@ packages: /babel-code-frame@6.26.0: resolution: {integrity: sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==} dependencies: - chalk: 1.1.3 + chalk: 4.1.2 esutils: 2.0.2 js-tokens: 3.0.2 dev: true @@ -1669,17 +1667,6 @@ packages: resolution: {integrity: sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==} dev: true - /chalk@1.1.3: - resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-styles: 2.2.1 - escape-string-regexp: 1.0.5 - has-ansi: 2.0.0 - strip-ansi: 3.0.1 - supports-color: 2.0.0 - dev: true - /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2358,11 +2345,6 @@ packages: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} dev: true - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: true - /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2987,13 +2969,6 @@ packages: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} dev: true - /has-ansi@2.0.0: - resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-regex: 2.1.1 - dev: true - /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -4738,6 +4713,10 @@ packages: strip-ansi: 7.1.0 dev: true + /string.fromcodepoint@0.2.1: + resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} + dev: false + /string.prototype.matchall@4.0.8: resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} dependencies: @@ -4788,13 +4767,6 @@ packages: safe-buffer: 5.2.1 dev: true - /strip-ansi@3.0.1: - resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-regex: 2.1.1 - dev: true - /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4824,11 +4796,6 @@ packages: engines: {node: '>=8'} dev: true - /supports-color@2.0.0: - resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} - engines: {node: '>=0.8.0'} - dev: true - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4998,6 +4965,12 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /unescape-js@1.1.4: + resolution: {integrity: sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==} + dependencies: + string.fromcodepoint: 0.2.1 + dev: false + /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} diff --git a/src/no-bad-emotes/index.js b/src/no-bad-emotes/index.js index 4c7dd69f..21481828 100644 --- a/src/no-bad-emotes/index.js +++ b/src/no-bad-emotes/index.js @@ -1,5 +1,6 @@ const { has, addWordSeparators, glob_to_regex, escape_regex } = FrankerFaceZ.utilities.object; +import unescape from 'unescape-js'; const COMMON_WORDS = require('./common.json'); @@ -31,6 +32,41 @@ class NoBadEmotes extends Addon { this.doesEmoteMatch = this.doesEmoteMatch.bind(this); // Settings + this.settings.add('addon.nobademotes.process-outgoing', { + default: false, + ui: { + path: 'Add-Ons > No Bad Emotes >> General', + title: 'Escape filtered emotes in outgoing chat messages.', + description: 'The filtering this add-on performs is client-only, so normally your filtering only affects what you see. If you enable this, any messages you send will be modified based on your filtering rules to prevent you from inadvertently using an emote that you have filtered out.', + component: 'setting-check-box' + } + }); + + this.settings.add('addon.nobademotes.outgoing-escape', { + default: '\\u{E0002}{NAME}', + process(ctx, val) { + try { + return unescape(val); + } catch(err) { + return `\u{E0002}{NAME}`; + } + }, + ui: { + path: 'Add-Ons > No Bad Emotes >> Advanced @{"sort": 99}', + title: 'Escape Character', + description: 'The character(s) to add to an emote\'s name when escaping filtered emotes in outgoing chat messages. Use `\'{\'NAME\'}\'` where the emote\'s name should go. Standard escape sequences are supported.', + component: 'setting-text-box', + validate(val) { + try { + unescape(val); + return true; + } catch(err) { + return false; + } + } + } + }); + this.settings.add('addon.nobademotes.no-filter-personal', { default: false, ui: { @@ -163,6 +199,8 @@ class NoBadEmotes extends Addon { // Call this before adding our filter so we only process once. this.onUseCommon(this.settings.get('addon.nobademotes.use-common')); + this.on('chat:pre-send-message', this.processOutbound); + this.emotes.addFilter({ type: 'nobademotes', test: this.doesEmoteMatch @@ -171,6 +209,7 @@ class NoBadEmotes extends Addon { onDisable() { this.emotes.removeFilter('nobademotes'); + this.off('chat:pre-send-message', this.processOutbound); } // Common Words @@ -229,6 +268,41 @@ class NoBadEmotes extends Addon { } + // Outbound Messages + + processOutbound(event) { + if ( ! this.settings.get('addon.nobademotes.process-outgoing') ) + return; + + const site = this.resolve('site'), + user = site?.getUser?.(); + + const bad_emotes = new Set; + + for(const set of this.emotes.getSets(user?.id, user?.login, null, event.channel)) { + for(const emote of Object.values(set.disabled_emotes)) { + if ( emote?.name ) + bad_emotes.add(emote.name); + } + } + + if ( ! bad_emotes.size ) + return; + + const out = [], + char = this.settings.get('addon.nobademotes.outgoing-escape'); + + for(const word of event.message.split(/ /g)) { + if ( bad_emotes.has(word) ) { + out.push(char.replace(/\{NAME\}/g, word)); + } else + out.push(word); + } + + event.message = out.join(' '); + } + + // Filtering doesEmoteMatch(emote, set) { diff --git a/src/no-bad-emotes/manifest.json b/src/no-bad-emotes/manifest.json index 4448577b..40b20ca6 100644 --- a/src/no-bad-emotes/manifest.json +++ b/src/no-bad-emotes/manifest.json @@ -1,7 +1,7 @@ { "enabled": true, "requires": [], - "version": "1.0.0", + "version": "1.1.0", "icon": "https://cdn.frankerfacez.com/badge/2/4/solid", "short_name": "NoBadEmotes", "name": "No Bad Emotes", @@ -9,5 +9,5 @@ "description": "This add-on removes undesired emotes from chat, so you only see the words. Tired of people using replacing stupidly common words like 'the' with emotes using platforms without any sort of common sense restrictions? Just grab this.", "settings": "add_ons.no_bad_emotes", "created": "2024-01-24T20:02:45.938Z", - "updated": "2024-01-24T20:02:45.938Z" + "updated": "2024-01-25T20:52:46.515Z" } \ No newline at end of file