您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
A script to slightly enhance everynoise's user experience
// ==UserScript== // @name Everynoise Enhancement Script // @namespace http://tampermonkey.net/ // @version 0.4.9 // @description A script to slightly enhance everynoise's user experience // @author NeroYuki // @match https://everynoise.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=https://everynoise.com/ // @license MIT // ==/UserScript== /** function highlight(which) { if (gdivs.length == 0) { gdivs = document.getElementsByClassName('genre'); } for (i=0; i<gdivs.length; i++) { thisdiv = gdivs[i]; thistext = thisdiv.firstChild.textContent; if (thisdiv.className.indexOf('scanme') > -1) { basename = 'genre scanme'; } else { basename = 'genre'; } if (which.length > 0 && which.trim() == thistext.trim().replace('"', '')) { thisdiv.className = basename + ' current'; if(typeof thisdiv.scrollIntoViewIfNeeded == 'function') { thisdiv.scrollIntoViewIfNeeded(); } else { thisdiv.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); } } else { thisdiv.className = basename; } } } **/ (function() { 'use strict'; // Your code here... var head = document.getElementsByTagName('head')[0]; var thisScript = document.scripts[0] var cloneScript= document.createElement('script'); if (thisScript) { // remove var scandur = 6000 cloneScript.textContent = thisScript.textContent.replace('var scandur = 6000;', ''); cloneScript.textContent = cloneScript.textContent.replace('function playx(key, genre, me) {', 'async function playx(key, genre, me) {'); cloneScript.textContent = cloneScript.textContent.replace('highlight(genre);', 'highlight(genre, sample_title);'); cloneScript.textContent = cloneScript.textContent.replace('function highlight(which)', 'function highlight(which, sample_title = "")'); cloneScript.textContent = cloneScript.textContent.replace('me.setAttribute(\'played\', clicknumber++);', ''); cloneScript.textContent = cloneScript.textContent.replace('async function playx(key, genre, me) {', 'async function playx(key, genre, me, loop = false) {'); cloneScript.textContent = cloneScript.textContent.replace('function scan(e, state) {' , 'function scan(e, state, scan_duration = 29000) { if (!scan_duration || scan_duration < 0) {scan_duration = 29000}'); cloneScript.textContent = cloneScript.textContent.replace('scan("continue")', 'scan("continue", document.getElementById("scan_length").value * 1000)'); cloneScript.textContent = cloneScript.textContent.replace('scan("stop")', 'scan("stop", document.getElementById("scan_length").value * 1000)'); cloneScript.textContent = cloneScript.textContent.replace('scandur = 6000', 'scandur = scan_duration'); cloneScript.textContent = cloneScript.textContent.replace('if (nowplaying == genre) {', 'if (nowplaying == genre && !loop) {'); cloneScript.textContent = cloneScript.textContent.replace('window.clearTimeout(nowpending);', 'if (!loop) { window.clearTimeout(nowpending); }'); cloneScript.textContent = cloneScript.textContent.replace('picked.onclick();', 'if (!isLooping) { window.clearTimeout(nowpending); picked.onclick(); } else { window.clearTimeout(nowpending); }'); cloneScript.textContent = cloneScript.textContent.replace('thisdiv.scrollIntoViewIfNeeded(false);', 'thisdiv.scrollIntoViewIfNeeded(true);'); var cloneScriptLines = cloneScript.textContent.split('\n'); var dataInsertLine = 0 cloneScriptLines.splice(dataInsertLine, 0, ` var genreData = []; fetch('https://raw.githubusercontent.com/NeroYuki/everynoise_enhancement_script/master/spotify_genres.json') .then(async (response) => { genreData = await response.json(); // if current path starts with engenremap, dont append genres if (window.location.pathname.startsWith('/engenremap')) { return; } // iterate genreData, if item have is_append, attempt to add it to html const appendGenres = genreData.filter(val => val.is_append).sort((a, b) => b.organic_index - a.organic_index); let id = 20000; // get the position of all genre divs const elements = document.getElementsByClassName('genre'); const positions = []; for (let i = 0; i < elements.length; i++) { const element = elements[i]; const rect = element.getBoundingClientRect(); const position = { top: parseInt(element.style.top.replace('px', '')), left: parseInt(element.style.left.replace('px', '')), width: rect.width, height: rect.height, id: element.id }; positions.push(position); } // sort the positions by top ascending positions.sort((a, b) => a.top - b.top); console.log(positions); for (const genre of appendGenres) { // select div class canvas and append the genre follow this template // <div id=item8 preview_url="https://p.scdn.co/mp3-preview/3c1278cf0eb6aba0f72552a3aa469dfed37d8e75" class="genre scanme" scan=true style="color: #c18f02; top: 3064px; left: 1170px; font-size: 130%" role=button tabindex=0 onKeyDown="kb(event);" onclick="playx("0AAl3LtvIhEilWXZmYHeh5", "reggaeton", this);" title="e.g. Zion "More"">reggaeton<a class=navlink href="engenremap-reggaeton.html" role=button tabindex=0 onKeyDown="kb(event);" onclick="event.stopPropagation();" >»</a> </div> const canvas = document.querySelector('.canvas'); const genreDiv = document.createElement('div'); genreDiv.className = 'genre scanme'; genreDiv.id = "item" + id++; genreDiv.setAttribute('scan', true); // color is array of hsv, use css hsl to set color genreDiv.style.color = \`hsl(\${genre.color[0]}, \${genre.color[1] * 100}%, \${genre.color[2] * 100}%)\`; // top should use the organic_index * canvas height, must not overlap with other elements const initialTop = genre.organic_index * canvas.clientHeight; genreDiv.style.top = \`\${genre.organic_index * canvas.clientHeight}px\`; // left should use the atmospheric_index * canvas width const initialLeft = genre.atmospheric_index * canvas.clientWidth; genreDiv.style.left = \`\${genre.atmospheric_index * canvas.clientWidth}px\`; // font-size should use popularity level genreDiv.style.fontSize = \`100%\`; genreDiv.setAttribute('role', 'button'); genreDiv.setAttribute('tabindex', 0); genreDiv.setAttribute('onKeyDown', 'kb(event);'); //let spotify_playlist_id = genre.spotify_playlist.replace('https://embed.spotify.com/?uri=spotify:playlist:', ''); genreDiv.setAttribute('onclick', \`playx("\${genre.track_id}", "\${genre.genre}", this);\`); genreDiv.setAttribute('title', \`e.g. \${genre.sample_song}\`); genreDiv.textContent = genre.genre; canvas.appendChild(genreDiv); // iterate from the the element with the same top position and check if it overlaps const rect = genreDiv.getBoundingClientRect(); let overlapCheckStartIndex = positions.findIndex(val => val.top + rect.height >= initialTop); // start checking from overlapCheckStartIndex, if any of it overlaps with the current element if (overlapCheckStartIndex !== -1) { let overlap = false; let adjustment_y = 0; for (let i = overlapCheckStartIndex; i < positions.length; i++) { const position = positions[i]; if ((position.top + position.height >= initialTop && position.top <= initialTop + rect.height) && (position.left + position.width >= initialLeft && position.left <= initialLeft + rect.width)) { overlap = true; adjustment_y = Math.max(adjustment_y, rect.height - (position.top - initialTop)); //console.log('overlap found, adjustment:', adjustment_y); } if (overlap) { // shift every element below the current element by the adjustment_y position.top += adjustment_y; } // in case position.top > top + 20, it is guaranteed that the rest of the elements will not overlap, break the loop else if (position.top > initialTop + rect.height) { break; } } } // add the new element itself to the positions array positions.push({ top: initialTop, left: initialLeft, width: rect.width, height: rect.height, id: genreDiv.id }); // sort the positions array again positions.sort((a, b) => a.top - b.top); } console.log(positions); // re-position the elements with the positions array for (let i = 0; i < positions.length; i++) { const position = positions[i]; const element = document.getElementById(position.id); element.style.top = position.top + 'px'; } // adjust canvas size to fit all elements const lastPosition = positions[positions.length - 1]; const canvasDiv = document.querySelector('.canvas'); canvasDiv.style.height = lastPosition.top + lastPosition.height + 'px'; }); var isLooping = false; const dbName = "everynoise_ext"; let db = null; const request = indexedDB.open(dbName, 3); request.onerror = (event) => { alert('error, please disable the userscript'); }; request.onsuccess = (event) => { db = event.target.result; db.transaction("genres") .objectStore("genres").get("pop").onsuccess = (event) => { const res = event.target.result if (!res) { console.log('adding data'); fetch('https://raw.githubusercontent.com/NeroYuki/everynoise_enhancement_script/master/spotify_genres_artists_map.json') .then(async (response) => { console.log('fetched artists genre mapping'); genreArtist = await response.json(); for (const genre of genreArtist) { db .transaction("genres", "readwrite") .objectStore("genres") .add(genre); } }); } } }; request.onupgradeneeded = (event) => { db = event.target.result; // delete the existing object store if (db.objectStoreNames.contains("genres")) { db.deleteObjectStore("genres"); } const objectStore = db.createObjectStore("genres", { keyPath: "genre" }); // Use transaction oncomplete to make sure the objectStore creation is // finished before adding data into it. objectStore.transaction.oncomplete = (event) => { console.log('creation done') }; }; function getData(dataStore, key) { return new Promise((resolve, reject) => { const dataFetch = db.transaction(dataStore) .objectStore(dataStore).get(key); dataFetch.onsuccess = (event) => { const res = event.target.result resolve(res); } dataFetch.onerror = (event) => { reject(event); } }) } `); var insertLoopLine = cloneScriptLines.findIndex(val => val.includes('var spotifyplayer = document.getElementById(\'spotifyplayer\');')) + 1; cloneScriptLines.splice(insertLoopLine, 0, ` console.log(genre, key); spotifyplayer.onended = () => { console.log('ended') playx(key, genre, me, true); }; `); var insertSelectionLine = cloneScriptLines.findIndex(val => val.includes('previewurl = me.getAttribute(\'preview_url\')')) + 1; cloneScriptLines.splice(insertSelectionLine, 0, ` let sample_title = me.getAttribute('title').replace('e.g. ', ''); const res = await getData('genres', genre) var selectionIndex = res?.artists ? Math.floor(Math.random() * res.artists.length + 1) : 0; if (selectionIndex > 1) { previewurl = res.artists[selectionIndex - 1].preview_url; sample_title = res.artists[selectionIndex - 1].sample_song; } `); var insertStartResetStateLine = cloneScriptLines.findIndex(val => val.includes('if (state == \'stop\') {')) - 1; cloneScriptLines.splice(insertStartResetStateLine, 0, ` if (state == 'start') { isLooping = false; } `); var insertLine = cloneScriptLines.findIndex(val => val.includes('thisdiv.scrollIntoView({behavior: "smooth", block: "center", inline: "center"});')) + 2; cloneScriptLines.splice(insertLine, 0, ` var label = document.getElementById('genre_label'); if (!label) { label = document.createElement('div'); document.body.appendChild(label); } var title_label = document.getElementById('title_label'); if (!title_label) { title_label = document.createElement('div'); document.body.appendChild(title_label); } var loop_label = document.getElementById('loop_label'); if (!loop_label) { loop_label = document.createElement('div'); document.body.appendChild(loop_label); } let genreInfo = genreData.find(val => val.genre === which.trim()) label.id = 'genre_label'; title_label.id = 'title_label'; loop_label.id = 'loop_label'; label.style.position = title_label.style.position = loop_label.style.position = 'absolute'; label.style.color = title_label.style.color = loop_label.style.color = 'black'; label.style.backgroundColor = title_label.style.backgroundColor = 'white'; loop_label.style.backgroundColor = isLooping ? 'green' : 'white'; label.style.padding = title_label.style.padding = loop_label.style.padding = '5px'; label.style.display = title_label.style.display = loop_label.style.display = 'none'; label.textContent = genreInfo ? genreInfo.desc : "No description"; title_label.textContent = '▶ ' + (sample_title ? sample_title : "Unknown title"); loop_label.textContent = '↻' // set the label position to be above the highlighted div var rect = thisdiv.getBoundingClientRect(); label.style.left = Math.max(10, (parseInt(thisdiv.style.left) - 100)) + 'px'; label.style.width = '300px' label.style.top = (parseInt(thisdiv.style.top) + 136) + 'px'; label.style.border = 'solid 1px black'; // show the label label.style.display = 'block'; title_label.style.left = Math.max(10, (parseInt(thisdiv.style.left) - 100)) + 'px'; title_label.style.top = (parseInt(thisdiv.style.top) + 76) + 'px'; title_label.style.border = 'solid 1px black'; // show the label title_label.style.display = sample_title ? 'block' : 'none'; loop_label.style.left = Math.max(10, (parseInt(thisdiv.style.left) - 124)) + 'px'; loop_label.style.top = (parseInt(thisdiv.style.top) + 76) + 'px'; loop_label.style.border = 'solid 1px black'; // show the label loop_label.style.display = 'block'; loop_label.onclick = () => { isLooping = !isLooping; loop_label.style.backgroundColor = isLooping ? 'green' : 'white'; } if(typeof label.scrollIntoViewIfNeeded == 'function') { label.scrollIntoViewIfNeeded(); } `); cloneScript.textContent = cloneScriptLines.join('\n'); head.appendChild(cloneScript); thisScript.remove(); } // find element with id "scan" and append a text input next to it var scanElement = document.getElementById('scan'); if (scanElement) { var inputElement = document.createElement('input'); inputElement.type = 'number'; inputElement.id = 'scan_length'; inputElement.placeholder = 'scan length'; inputElement.style.marginLeft = '10px'; inputElement.style.width = '80px'; scanElement.parentNode.insertBefore(inputElement, scanElement.nextSibling); } // change scan element's onclick property string to replace scan(event, 'start') and scan(event, 'stop') to scan(event, 'start', <scan_length>) and scan(event, 'stop', <scan_length>) if (scanElement) { scanElement.removeAttribute('onclick'); // add event listener to scan element // if (this.innerText == 'scan') {scan(event, 'start');this.innerHTML = 'stop';} else {scan(event, 'stop');this.innerHTML = 'scan';} scanElement.addEventListener('click', function() { if (this.innerText == 'scan') { scan(event, 'start', document.getElementById('scan_length').value * 1000); this.innerHTML = 'stop'; } else { scan(event, 'stop', document.getElementById('scan_length').value * 1000); this.innerHTML = 'scan'; } }); } })();