From 5e79e9e0f24f9c61dfeca639712036e7d99e7a5f Mon Sep 17 00:00:00 2001 From: Yazazuyo <63968466+MiepHD@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:13:56 +0100 Subject: [PATCH] feat: Add new plugin `Album actions` (#1515) Co-authored-by: JellyBrick --- src/i18n/resources/en.json | 4 + src/plugins/album-actions/index.ts | 178 ++++++++++++++++++ .../album-actions/templates/dislike.html | 74 ++++++++ src/plugins/album-actions/templates/like.html | 74 ++++++++ .../album-actions/templates/undislike.html | 74 ++++++++ .../album-actions/templates/unlike.html | 74 ++++++++ 6 files changed, 478 insertions(+) create mode 100644 src/plugins/album-actions/index.ts create mode 100644 src/plugins/album-actions/templates/dislike.html create mode 100644 src/plugins/album-actions/templates/like.html create mode 100644 src/plugins/album-actions/templates/undislike.html create mode 100644 src/plugins/album-actions/templates/unlike.html diff --git a/src/i18n/resources/en.json b/src/i18n/resources/en.json index 04134d43..3e8a2b87 100644 --- a/src/i18n/resources/en.json +++ b/src/i18n/resources/en.json @@ -593,6 +593,10 @@ "visualizer-type": "Visualizer Type" }, "name": "Visualizer" + }, + "album-actions": { + "description": "Adds Undislike, Dislike, Like, and Unlike buttons to apply this to all songs in a playlist or album.", + "name": "Album actions" } } } diff --git a/src/plugins/album-actions/index.ts b/src/plugins/album-actions/index.ts new file mode 100644 index 00000000..48bdc302 --- /dev/null +++ b/src/plugins/album-actions/index.ts @@ -0,0 +1,178 @@ +import { t } from '@/i18n'; +import { createPlugin } from '@/utils'; +import { ElementFromHtml } from '@/plugins/utils/renderer'; + +import undislikeHTML from './templates/undislike.html?raw'; +import dislikeHTML from './templates/dislike.html?raw'; +import likeHTML from './templates/like.html?raw'; +import unlikeHTML from './templates/unlike.html?raw'; + +export default createPlugin({ + name: () => t('plugins.album-actions.name'), + description: () => t('plugins.album-actions.description'), + restartNeeded: false, + config: { + enabled: true, + }, + renderer: { + observer: null as MutationObserver | null, + loadObserver: null as MutationObserver | null, + changeObserver: null as MutationObserver | null, + waiting: false as boolean, + start() { + //Waits for pagechange + this.onPageChange(); + this.observer = new MutationObserver(() => { + this.onPageChange(); + }); + this.observer.observe(document.querySelector('#browse-page'), { + attributes: false, + childList: true, + subtree: true, + }); + }, + onPageChange() { + if (this.waiting) { + return; + } else { + this.waiting = true; + } + this.waitForElem('#continuations').then((continuations: HTMLElement) => { + this.waiting = false; + //Gets the for buttons + let buttons: Array = [ + ElementFromHtml(undislikeHTML), + ElementFromHtml(dislikeHTML), + ElementFromHtml(likeHTML), + ElementFromHtml(unlikeHTML), + ]; + //Finds the playlist + const playlist = + document.querySelector('ytmusic-shelf-renderer') ?? + document.querySelector('ytmusic-playlist-shelf-renderer'); + //Adds an observer for every button so it gets updated when one is clicked + this.changeObserver?.disconnect(); + this.changeObserver = new MutationObserver(() => { + this.stop(); + this.start(); + }); + const allButtons = playlist.querySelectorAll( + 'yt-button-shape.ytmusic-like-button-renderer', + ); + for (const btn of allButtons) + this.changeObserver.observe(btn, { + attributes: true, + childList: false, + subtree: false, + }); + //Determine if button is needed and colors the percentage + const listsLength = playlist.querySelectorAll( + '#button-shape-dislike > button', + ).length; + if (continuations.children.length == 0 && listsLength > 0) { + const counts = [ + playlist?.querySelectorAll( + '#button-shape-dislike[aria-pressed=true] > button', + ).length, + playlist?.querySelectorAll( + '#button-shape-dislike[aria-pressed=false] > button', + ).length, + playlist?.querySelectorAll( + '#button-shape-like[aria-pressed=false] > button', + ).length, + playlist?.querySelectorAll( + '#button-shape-like[aria-pressed=true] > button', + ).length, + ]; + let i = 0; + for (const count of counts) { + if (count == 0) { + buttons.splice(i, 1); + i--; + } else { + buttons[i].children[0].children[0].style.setProperty( + '-webkit-mask-size', + `100% ${100 - (count / listsLength) * 100}%`, + ); + } + i++; + } + } + const menu = document.querySelector('.detail-page-menu'); + if (menu && !document.querySelector('.like-menu')) { + for (const button of buttons) { + menu.appendChild(button); + button.addEventListener('click', this.loadFullList); + } + } + }); + }, + loadFullList(event) { + event.stopPropagation(); + const id: string = event.currentTarget.id, + loader = document.getElementById('continuations'); + this.loadObserver = new MutationObserver(() => { + this.applyToList(id, loader); + }); + this.applyToList(id, loader); + this.loadObserver.observe(loader, { + attributes: true, + childList: true, + subtree: true, + }); + loader?.style.setProperty('top', '0'); + loader?.style.setProperty('left', '50%'); + loader?.style.setProperty('position', 'absolute'); + }, + applyToList(id: string, loader: HTMLElement) { + if (loader.children.length != 0) return; + this.loadObserver?.disconnect(); + let playlistbuttons: NodeListOf | undefined; + const playlist = document.querySelector('ytmusic-shelf-renderer') + ? document.querySelector('ytmusic-shelf-renderer') + : document.querySelector('ytmusic-playlist-shelf-renderer'); + switch (id) { + case 'allundislike': + playlistbuttons = playlist?.querySelectorAll( + '#button-shape-dislike[aria-pressed=true] > button', + ); + break; + case 'alldislike': + playlistbuttons = playlist?.querySelectorAll( + '#button-shape-dislike[aria-pressed=false] > button', + ); + break; + case 'alllike': + playlistbuttons = playlist?.querySelectorAll( + '#button-shape-like[aria-pressed=false] > button', + ); + break; + case 'allunlike': + playlistbuttons = playlist?.querySelectorAll( + '#button-shape-like[aria-pressed=true] > button', + ); + break; + default: + } + playlistButtons?.forEach((elem) => elem.click()); + }, + stop() { + this.observer?.disconnect(); + this.changeObserver?.disconnect(); + for (const button of document.querySelectorAll('.like-menu')) { + button.remove(); + } + }, + waitForElem(selector: string) { + return new Promise((resolve) => { + const interval = setInterval(() => { + const elem = document.querySelector(selector); + if (!elem) return; + + clearInterval(interval); + resolve(elem); + }); + }); + }, + }, +}); diff --git a/src/plugins/album-actions/templates/dislike.html b/src/plugins/album-actions/templates/dislike.html new file mode 100644 index 00000000..289812d3 --- /dev/null +++ b/src/plugins/album-actions/templates/dislike.html @@ -0,0 +1,74 @@ + diff --git a/src/plugins/album-actions/templates/like.html b/src/plugins/album-actions/templates/like.html new file mode 100644 index 00000000..d7fd78a9 --- /dev/null +++ b/src/plugins/album-actions/templates/like.html @@ -0,0 +1,74 @@ + diff --git a/src/plugins/album-actions/templates/undislike.html b/src/plugins/album-actions/templates/undislike.html new file mode 100644 index 00000000..83d1295d --- /dev/null +++ b/src/plugins/album-actions/templates/undislike.html @@ -0,0 +1,74 @@ + diff --git a/src/plugins/album-actions/templates/unlike.html b/src/plugins/album-actions/templates/unlike.html new file mode 100644 index 00000000..6f93dbaf --- /dev/null +++ b/src/plugins/album-actions/templates/unlike.html @@ -0,0 +1,74 @@ +