From 1f1337c45e525c3f1a8af98e92eb49f3e8d69aa3 Mon Sep 17 00:00:00 2001 From: thakyZ <950594+thakyZ@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:24:49 -0800 Subject: [PATCH 1/3] add: copy code add-on --- src/copy_code/index.jsx | 180 ++++++++++++++++++++++++++++++++++++ src/copy_code/manifest.json | 12 +++ 2 files changed, 192 insertions(+) create mode 100644 src/copy_code/index.jsx create mode 100644 src/copy_code/manifest.json diff --git a/src/copy_code/index.jsx b/src/copy_code/index.jsx new file mode 100644 index 00000000..ac9b7e5e --- /dev/null +++ b/src/copy_code/index.jsx @@ -0,0 +1,180 @@ +// noinspection JSUnresolvedVariable +const { createElement } = FrankerFaceZ.utilities.dom; + +class CopyCode extends Addon { + constructor(...args) { + super(...args); + this.inject('i18n'); + this.inject('site.elemental'); + + /** @type {RegExp} */ + this.codeRegex = /Click here to redeem: (.*?)\./; + /** @type {HTMLDivElement | undefined} */ + this.inboxPopup = undefined; + /** @type {string} */ + this.notificationSelector = 'body > div div[role="dialog"] div.center-window > div:last-child > div.simplebar-scroll-content div[data-test-selector="center-window__content"] > div:first-child > div:not(:first-child) > div'; + /** @type {ElementalWrapper} */ + this.Root = this.elemental.define( + 'notification', this.notificationSelector, + ['notification'], + {childNodes: true, subtree: true}, 1, 30000, false); + /** @type {Record} */ + this.cache = {}; + /** @type {HTMLDivElement[]} */ + this.buttons = []; + } + + onEnable() { + this.Root.on('mount', this.updateNotifications, this); + this.Root.on('mutate', this.updateNotifications, this); + this.Root.each(element => this.updateNotifications(element)); + } + + onDisable() { + this.destroyButtons(); + } + + /** + * Updates notifications menus. + * @param {HTMLDivElement} element The element that was mutated or mounted. + */ + updateNotifications(element) { + try { + this.updateButton(element); + } catch (error) { + this.log.error(error); + } + } + + /** + * Destroys all button instances. + */ + destroyButtons() { + const count = this.buttons.length; + for (let i = count - 1; i >= 0; i--) { + this.log.info('pop', i); + this.log.info('remove', this.buttons[i]); + this.log.info('count', count); + this.buttons[i]?.remove(); + this.buttons.pop(); + } + } + + /** + * Creates all the button and/or updates them. + * @param {HTMLDivElement} element The element that was mutated or mounted. + * @returns {void} Nothing... + */ + updateButton(element) { + /** @type {HTMLParagraphElement | undefined} */ + const notificationBody = element.querySelector('div[data-test-selector="persistent-notification__body"] > span > p'); + + if (!notificationBody) + return; + + /** @type {HTMLDivElement | undefined} */ + const button = element.querySelector('.ffz--copy-code-button'); + + if (button) + return; + + if (!this.codeRegex.test(notificationBody.innerText)) + return; + + if (this.cache[notificationBody.innerText] === undefined) { + /** @type {string[] | null} */ + const match = notificationBody.innerText.match(this.codeRegex); + + if (match === null || match.length != 2) + return; + + /** @type {string} */ + this.cache[notificationBody.innerText] = match[1]; + } + + /** @type {string} */ + const code = this.cache[notificationBody.innerText]; + + /** @type {string} */ + const label = this.i18n.t('addon.copy_code.button.label', 'Copy redeem code'); + + /** @type {HTMLDivElement} */ + const cont = (
+ + +
); + + this.buttons.push(cont); + + /** @type {HTMLDivElement | undefined} */ + const thing = element.querySelector('a > div > div'); + if (thing) { + thing.appendChild(cont); + } + } + + /** + * Finds the parent element based on the selector. + * @param {HTMLElement} element the element to find the parent of. + * @param {string} selector the selector to check for if it is the element. + * @returns {HTMLElement | undefined} the element if found; otherwise undefined. + */ + findParent(element, selector) { + /** @type {HTMLElement | undefined} */ + let found; + /** @type {HTMLElement | undefined} */ + let lastParent = element.parentElement; + + while (!found && lastParent) { + if (lastParent.nodeName === 'HTML' || lastParent.nodeName === 'BODY') { + lastParent = undefined; + } + if (lastParent.nodeName.toLowerCase() === selector.toLowerCase()) { + found = lastParent; + } else if (document.querySelector(selector) === lastParent) { + found = lastParent; + } else { + lastParent = lastParent.parentElement; + } + } + return found; + } + + /** + * Handles the copy button click. + * @param {Event} event The click event that fires this method. + */ + onButtonClick(event) { + // Prevent bubbling. + event.preventDefault(); + try { + /** @type {string | undefined} */ + const code = this.findParent(event.target, 'button').getAttribute('data-code'); + if (!code) + throw new Error('could not get attribute, "data-code", on event target'); + try { + navigator.clipboard.writeText(code); + } catch (err) { + this.log.error('Clipboard is not accessible', err); + } + } catch (err) { + this.log.error('Failed to copy redeem code.', event, err); + } + } +} + +CopyCode.register(); \ No newline at end of file diff --git a/src/copy_code/manifest.json b/src/copy_code/manifest.json new file mode 100644 index 00000000..0698c1f6 --- /dev/null +++ b/src/copy_code/manifest.json @@ -0,0 +1,12 @@ +{ + "enabled": true, + "requires": [], + "version": "1.0.0", + "short_name": "CopyCode", + "name": "Copy Code", + "author": "Neko Boi Nick", + "website": "https://github.com/thakyZ/FrankerFaceZ_Add-Ons", + "description": "Adds a copy code button to notifications with a redeem code.", + "created": "2024-12-04T22:24:51.713Z", + "updated": "2024-12-04T22:24:51.713Z" +} \ No newline at end of file From 52e55c431e788dbf87a9a0b4a3fa3b060789f357 Mon Sep 17 00:00:00 2001 From: thakyZ <950594+thakyZ@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:06:46 -0800 Subject: [PATCH 2/3] update: fixed code hopefully now will have a friend check it --- src/copy_code/index.jsx | 75 ++++++++++++++++++++++--------------- src/copy_code/manifest.json | 2 +- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/copy_code/index.jsx b/src/copy_code/index.jsx index ac9b7e5e..9007d262 100644 --- a/src/copy_code/index.jsx +++ b/src/copy_code/index.jsx @@ -5,56 +5,73 @@ class CopyCode extends Addon { constructor(...args) { super(...args); this.inject('i18n'); - this.inject('site.elemental'); /** @type {RegExp} */ this.codeRegex = /Click here to redeem: (.*?)\./; /** @type {HTMLDivElement | undefined} */ this.inboxPopup = undefined; /** @type {string} */ - this.notificationSelector = 'body > div div[role="dialog"] div.center-window > div:last-child > div.simplebar-scroll-content div[data-test-selector="center-window__content"] > div:first-child > div:not(:first-child) > div'; - /** @type {ElementalWrapper} */ - this.Root = this.elemental.define( - 'notification', this.notificationSelector, - ['notification'], - {childNodes: true, subtree: true}, 1, 30000, false); + /** @type {FineWrapper} */ + // this.RootFine = this.fine.define( + // 'notification', () => true); /** @type {Record} */ this.cache = {}; /** @type {HTMLDivElement[]} */ this.buttons = []; } + /** + * Triggered on enable of the add-on + */ onEnable() { - this.Root.on('mount', this.updateNotifications, this); - this.Root.on('mutate', this.updateNotifications, this); - this.Root.each(element => this.updateNotifications(element)); + this.notificationObserver = new MutationObserver(this.notificationObserverCallback.bind(this)); + this.notificationObserver.observe(document.body, { childList: true, subtree: true, attributes: true }); } - onDisable() { - this.destroyButtons(); + elementIs(element, ...queries) { + for (const query of queries) { + const queried = document.querySelector(query); + if (queried == element) { + return true; + } + } + return false; } /** * Updates notifications menus. - * @param {HTMLDivElement} element The element that was mutated or mounted. + * @param {MutationRecord[]} mutations The list of mutations + * @param {MutationObserver} _observer The mutation observer */ - updateNotifications(element) { - try { - this.updateButton(element); - } catch (error) { - this.log.error(error); + /* eslint-disable-next-line no-unused-vars */ + notificationObserverCallback(mutations, _observer) { + for (const mutation of mutations) { + if ((mutation.type === 'childList' && mutation.addedNodes.length > 0 && this.elementIs(mutation.target, 'div[data-test-selector="center-window__content"]')) || this.elementIs(mutation.target, '.ReactModal__Body--open')) { + for (const element of mutation.addedNodes) { + for (const innerElement of element.querySelectorAll('div.simplebar-scroll-content div[data-test-selector="center-window__content"] > div > div:not(:first-child) > div')) { + this.updateButton(innerElement); + } + } + } } } + /** + * Triggered on disabling of the add-on + */ + onDisable() { + this.notificationObserver.disconnect(); + this.destroyButtons(); + } + /** * Destroys all button instances. */ destroyButtons() { + // statically store the count of how many buttons. const count = this.buttons.length; + // Loop from end to the beginning of the array as pop() removes the last one. for (let i = count - 1; i >= 0; i--) { - this.log.info('pop', i); - this.log.info('remove', this.buttons[i]); - this.log.info('count', count); this.buttons[i]?.remove(); this.buttons.pop(); } @@ -66,27 +83,25 @@ class CopyCode extends Addon { * @returns {void} Nothing... */ updateButton(element) { + if (!element) return; + /** @type {HTMLParagraphElement | undefined} */ const notificationBody = element.querySelector('div[data-test-selector="persistent-notification__body"] > span > p'); - if (!notificationBody) - return; + if (!notificationBody) return; /** @type {HTMLDivElement | undefined} */ const button = element.querySelector('.ffz--copy-code-button'); - if (button) - return; + if (button) return; - if (!this.codeRegex.test(notificationBody.innerText)) - return; + if (!this.codeRegex.test(notificationBody.innerText)) return; if (this.cache[notificationBody.innerText] === undefined) { /** @type {string[] | null} */ const match = notificationBody.innerText.match(this.codeRegex); - if (match === null || match.length != 2) - return; + if (match === null || match.length != 2) return; /** @type {string} */ this.cache[notificationBody.innerText] = match[1]; @@ -169,7 +184,7 @@ class CopyCode extends Addon { try { navigator.clipboard.writeText(code); } catch (err) { - this.log.error('Clipboard is not accessible', err); + this.log.error('Clipboard is not accessible', event, err); } } catch (err) { this.log.error('Failed to copy redeem code.', event, err); diff --git a/src/copy_code/manifest.json b/src/copy_code/manifest.json index 0698c1f6..48dd69a8 100644 --- a/src/copy_code/manifest.json +++ b/src/copy_code/manifest.json @@ -8,5 +8,5 @@ "website": "https://github.com/thakyZ/FrankerFaceZ_Add-Ons", "description": "Adds a copy code button to notifications with a redeem code.", "created": "2024-12-04T22:24:51.713Z", - "updated": "2024-12-04T22:24:51.713Z" + "updated": "2024-12-09T18:06:48.088Z" } \ No newline at end of file From bbad96e34a473c5c63b1e9d8ac3a99d1f9d4e48d Mon Sep 17 00:00:00 2001 From: thakyZ <950594+thakyZ@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:58:56 -0800 Subject: [PATCH 3/3] update: fixed friend unable to use as span element was missing --- src/copy_code/index.jsx | 2 +- src/copy_code/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/copy_code/index.jsx b/src/copy_code/index.jsx index 9007d262..837a6349 100644 --- a/src/copy_code/index.jsx +++ b/src/copy_code/index.jsx @@ -86,7 +86,7 @@ class CopyCode extends Addon { if (!element) return; /** @type {HTMLParagraphElement | undefined} */ - const notificationBody = element.querySelector('div[data-test-selector="persistent-notification__body"] > span > p'); + const notificationBody = element.querySelector('div[data-test-selector="persistent-notification__body"] p'); if (!notificationBody) return; diff --git a/src/copy_code/manifest.json b/src/copy_code/manifest.json index 48dd69a8..ee52a0f3 100644 --- a/src/copy_code/manifest.json +++ b/src/copy_code/manifest.json @@ -8,5 +8,5 @@ "website": "https://github.com/thakyZ/FrankerFaceZ_Add-Ons", "description": "Adds a copy code button to notifications with a redeem code.", "created": "2024-12-04T22:24:51.713Z", - "updated": "2024-12-09T18:06:48.088Z" + "updated": "2024-12-09T19:58:58.065Z" } \ No newline at end of file