Closed zjeffer closed 4 years ago
Managed to make your script work, I added a MutationObserver so if the main-view div changes (new page load) it will trigger the function. I can't seem to make the Artist Page to work though since it doesn't seem to load the td element on first load but it will work if you resize the window.
(function numberOfPlays() {
function changeToNumber() {
//if page is fully loaded or something idk
if (!Spicetify.LocalStorage) {
//if it isn't wait until it is
setTimeout(changeToNumber, 5000);
return;
}
//select all table rows
let cells = document.querySelectorAll(".Table__table tbody tr.TableRow.TableRow td:last-child > div");
//check if the current window is the album view or the artist view
if (cells != undefined && cells.length != 0) {
//for album view
console.log(cells)
cells.forEach(c => {
//get amount from attribute
let plays = c.getAttribute("data-tooltip-text");
//change cell value
c.parentNode.innerHTML = plays.substring(0, plays.indexOf("plays") - 1);
})
} else {
//for artist view
//get document inside #app-artist iframe
let doc = document.querySelector("#app-artist").contentWindow.document;
//search doc for all cells
cells = doc.querySelectorAll(".tl-body tr .tl-popularity:last-child");
for(let cell of cells){
//get amount from attribute
let plays = cell.getAttribute("data-tooltip")
//change cell value
cell.innerHTML = plays.substring(0, plays.indexOf("plays") - 1);
}
}
console.log(cells)
}
const toCheckMutate = document.getElementById('view-content');
const config = { attributes: true, childList: true, subtree: true };
let observerCallback = function(mutationsList, _) {
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
changeToNumber()
}
else if (mutation.type === 'attributes') {
changeToNumber()
}
}
};
let observer = new MutationObserver(observerCallback)
observer.observe(toCheckMutate, config)
})();
Thanks!
I think the reason it doesn't work is because the artist page is wrapped in an iframe element, creating a document within a document. I'll keep searching.
(function numberOfPlays() {
function changeToNumber() {
//if page is fully loaded or something idk what this actually does
if (!Spicetify.LocalStorage) {
//if it isn't try again every 1000 millis until it is
setTimeout(changeToNumber, 1000);
return;
}
//select all table rows
let albumCells = document.querySelectorAll(".Table__table tbody tr.TableRow.TableRow td:last-child");
//check if the current window is the album view or the artist view
if (albumCells != undefined && albumCells.length != 0) {
//for album view
for (let cell of albumCells) {
//get amount from attribute
let plays = cell.firstChild.getAttribute("data-tooltip-text");
if (plays != null) {
//change cell value
cell.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
}
}
//for artist view
//get document inside #app-artist iframe
let doc = document.querySelector("#app-artist");
if (doc != undefined && doc != null) {
doc = doc.contentWindow.document;
//search doc for all artistCells
let artistCells = doc.querySelectorAll(".tl-body tr .tl-popularity:last-child");
for (let cell of artistCells) {
//get amount from attribute
let plays = cell.getAttribute("data-tooltip");
//change cell value
cell.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
}
}
const toCheckMutate = document.getElementById("view-content");
const config = { attributes: true, childList: true, subtree: true };
let observerCallback = function(mutationsList, _) {
for (let mutation of mutationsList) {
if (mutation.type === "childList" || mutation.type === "attributes") {
changeToNumber();
}
}
};
let observer = new MutationObserver(observerCallback);
observer.observe(toCheckMutate, config);
const checkArtist = document.querySelector("#app-artist").contentDocument.body;
let artistObserver = new MutationObserver(observerCallback);
artistObserver.observe(checkArtist, config);
})();
I added a new MutationObserver for the body of the iframe object, but the problem is that document.querySelector("#app-artist")
returns null.
Any ideas?
@zjeffer Hey, after checking on the Spicetify object wrapper and reading some other extensions you can add an event listener and listen on app change. The callback will receive an object with a container key which the value is the iframe of the current app.
{id: "playlist", uri: "spotify:app:playlist:37i9dQZF1Eaa1PQGW8c4gW", isEmbeddedApp: false, container: iframe#app-playlist.active}
Spicetify.Player.addEventListener("appchange", ({"data": data}) => {
if (data.id == "playlist") {
console.log(data.container)
}
})
Thanks!
Updated script with appchange events:
(function numberOfPlays() {
if (!Spicetify.LocalStorage) {
setTimeout(numberOfPlays, 1000);
return;
}
console.log("loaded");
function albumPopularity(data) {
//select all table rows
let albumCells = data.querySelectorAll(".Table__table tbody tr.TableRow.TableRow td:last-child");
console.log(albumCells);
//check if the current window is the album view or the artist view
if (albumCells != undefined && albumCells.length != 0) {
//for album view
for (let cell of albumCells) {
//get amount from attribute
let plays = cell.firstChild.getAttribute("data-tooltip-text");
if (plays != null) {
//change cell value
cell.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
}
}
}
function artistPopularity(data) {
//search doc for all artistCells
let artistCells = data.children[0].querySelectorAll(".tl-body tr .tl-popularity");
console.log(artistCells);
for (let cell of artistCells) {
//get amount from attribute
let plays = cell.getAttribute("data-tooltip");
//change cell value
cell.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
}
function onChange(data) {
if (data.id == "artist") {
console.log("artistdata:");
console.log(data);
artistPopularity(data.container.contentDocument.body);
} else if (data.id == "album") {
console.log("albumdata:");
console.log(data);
albumPopularity(data.container.children[0]);
}
}
Spicetify.Player.addEventListener("appchange", ({ data: data }) => {
console.log("appchange");
onChange(data);
});
//this doesn't do anything for some reason (wrong object to listen to?)
Spicetify.Player.addEventListener("scroll", ({ data: data }) => {
console.log("scroll");
onChange(data);
});
})();
Now when I go to an album with a lot of songs, scroll down and back up, the numbers disappear again.
On an artist's page, the list of cells is often empty for some reason, so the script doesn't find the popularity bars and doesn't do anything. It does sometimes work but I don't know how to make it always work. Scrolling down an artist's page also reveals new albums, but doesn't change the bars because revealing new albums doesn't trigger an appchange event.
I tried adding a scroll event listener to Spicetify.Player, but it never triggers.
I'll keep debugging.
function onChange(data) {
if (data.id == "artist") {
console.log("artistdata:");
console.log(data);
artistPopularity(data.container.contentDocument.body);
} else if (data.id == "album") {
console.log("albumdata:");
console.log(data);
albumPopularity(data.container.children[0]);
const observer = new MutationObserver(() => {
albumPopularity(data.container.children[0]);
});
observer.observe(
data.container.children[0].querySelector(".Table__table"),
{
childList: true,
subtree: true
}
);
}
}
Great idea! Looking forward for a debuged script
This is my current script:
It works on albums, but on artist's pages it only works on the first few albums, so it needs some fixing and optimizing.
Sadly I'm busy with school stuff, so that's why it's not done yet.
(function numberOfPlays() {
if (!Spicetify.LocalStorage) {
setTimeout(numberOfPlays, 1000);
return;
}
console.log("loaded");
function albumPopularity(data) {
//select all table rows
let albumCells = data.querySelectorAll(".Table__table tbody tr.TableRow.TableRow td:last-child");
console.log(albumCells);
//check if the current window is the album view or the artist view
if (albumCells != undefined && albumCells.length != 0) {
//for album view
for (let cell of albumCells) {
//get amount from attribute
let plays = cell.firstChild.getAttribute("data-tooltip-text");
if (plays != null) {
//change cell value
cell.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
}
}
}
function artistPopularity(data) {
//search doc for all artistCells
let artistCells = data.children[0].querySelectorAll(".tl-body tr .tl-popularity");
console.log(artistCells);
for (let cell of artistCells) {
//get amount from attribute
let plays = cell.getAttribute("data-tooltip");
//change cell value
cell.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
}
function onChange(data) {
if (data.id == "artist") {
console.log("artistdata:");
console.log(data);
artistPopularity(data.container.contentDocument);
const observer = new MutationObserver(() => {
artistPopularity(data.container.contentDocument);
});
observer.observe(data.container.children[0].querySelector(".albums ul"), {
childList: true,
subtree: true,
});
} else if (data.id == "album") {
console.log("albumdata:");
console.log(data);
albumPopularity(data.container.children[0]);
const observer = new MutationObserver(() => {
albumPopularity(data.container.children[0]);
});
observer.observe(data.container.children[0].querySelector(".Table__table"), {
childList: true,
subtree: true,
});
}
}
Spicetify.Player.addEventListener("appchange", ({ data: data }) => {
console.log("appchange");
onChange(data);
});
// this function needs fixing
const iframeInterval = setInterval(() => {
/** @type {HTMLIFrameElement} */
console.log("interval");
const currentIframe = document.querySelector("iframe.active");
if (!currentIframe) {
return;
}
if (currentIframe.id === "app-collection-songs") {
console.log("clearing interval");
clearInterval(iframeInterval);
return;
}
if (currentIframe.id !== "app-artist") {
return;
}
let cells = currentIframe.contentDocument.querySelectorAll(".tl-body tr .tl-popularity");
for (let c of cells) {
//get amount from attribute
let plays = c.getAttribute("data-tooltip");
//change cell value
c.innerHTML = `<div>${plays.substring(0, plays.indexOf("plays") - 1)}</div>`;
}
console.log("iframe cells:");
console.log(cells);
if (cells.length > 0) {
// const observer = new MutationObserver(() => {
// artistPopularity(cells[0].ownerDocument);
// });
// let node = cells[0].ownerDocument.querySelector(".albums");
// console.log(node);
// observer.observe(node, {
// childList: true,
// subtree: true,
// });
console.log("clearing interval");
clearInterval(iframeInterval);
}
}, 500);
})();
@khanhas I have the same problem with my fork of Dribblish I'm listening to "appchange" but I noticed that sometime the event is not fired, exactly when I get a warning message
home
DevTools failed to load SourceMap: Could not load content for https://zlink.app.spotify.com/zlink.bundle.js.map: Connection error: net::ERR_NAME_NOT_RESOLVED
DevTools failed to load SourceMap: Could not load content for https://queue.app.spotify.com/queue.bundle.js.map: Connection error: net::ERR_NAME_NOT_RESOLVED
queue
radio-hub
DevTools failed to load SourceMap: Could not load content for https://genius.app.spotify.com/lyrics.bundle.js.map: Connection error: net::ERR_NAME_NOT_RESOLVED
genius
made-for-you
recently-played
home
DevTools failed to load SourceMap: Could not load content for https://search.app.spotify.com/search.bundle.js.map: Connection error: net::ERR_NAME_NOT_RESOLVED
For future reference, this will listen to navigation_request_state changes.
window.addEventListener("message", ({ data: info }) => {
if (info.type == "navigation_request_state" && info.method == "open") {
console.log("Opened page:", JSON.parse(info.state).uri);
}
});
@reteps I'm using your eventListener in Dribblish-Dynamic, however I noticed it's missing several page changes. For example the "Preferences" panel. Removing the test on info.type
& info.method
does the trick but then I end up triggering the attached function very often. Do you have any tips to share?
Thanks
This is my script: It changes the popularity meter in album views so instead of seeing a bar, you see the number of plays each song has.
The problem is that when I select all tables with
document.querySelectorAll(".Table__table tbody tr.TableRow.TableRow td:last-child");
, it always returns an empty list, unless I reload the page with ctrl+shift+R.How can I run my script everytime a new page in spotify loads (whenever I visit an album or an artist page with albums)?