BetterYTM.user.js 40 KB

123456789101112131415161718192021222324252627282930313233343536
  1. // ==UserScript==
  2. // @name BetterYTM
  3. // @homepageURL https://github.com/Sv443/BetterYTM#readme
  4. // @namespace https://github.com/Sv443/BetterYTM
  5. // @version 1.0.0
  6. // @description Configurable layout and UX improvements for YouTube Music
  7. // @description:de Konfigurierbares Layout und UX-Verbesserungen für YouTube Music
  8. // @license MIT
  9. // @author Sv443
  10. // @copyright Sv443 (https://github.com/Sv443)
  11. // @icon https://raw.githubusercontent.com/Sv443/BetterYTM/main/assets/icon/icon.png
  12. // @match https://music.youtube.com/*
  13. // @match https://www.youtube.com/*
  14. // @run-at document-start
  15. // @downloadURL https://raw.githubusercontent.com/Sv443/BetterYTM/main/dist/BetterYTM.user.js
  16. // @updateURL https://raw.githubusercontent.com/Sv443/BetterYTM/main/dist/BetterYTM.user.js
  17. // @connect api.sv443.net
  18. // @grant GM.getValue
  19. // @grant GM.setValue
  20. // @grant unsafeWindow
  21. // ==/UserScript==
  22. /*
  23. ▄▄▄ ▄ ▄▄▄▄▄▄ ▄
  24. █ █ ▄▄▄ █ █ ▄▄▄ ▄ ▄█ █ █ █▀▄▀█
  25. █▀▀▄ █▄█ █▀ █▀ █▄█ █▀ █ █ █ █
  26. █▄▄▀ ▀▄▄ ▀▄▄ ▀▄▄ ▀▄▄ █ █ █ █ █
  27. Made with ❤️ by Sv443
  28. I welcome every contribution on GitHub!
  29. https://github.com/Sv443/BetterYTM
  30. */
  31. /* Disclaimer: I am not affiliated with YouTube, Google, Alphabet, Genius or anyone else */
  32. /* C&D this 🖕 */
  33. var e={546:function(e,t){var n=function(){function e(){this._eventHandlers={}}return e.prototype.isValidType=function(e){return"string"==typeof e},e.prototype.isValidHandler=function(e){return"function"==typeof e},e.prototype.on=function(e,t){if(!e||!t)return!1;if(!this.isValidType(e))return!1;if(!this.isValidHandler(t))return!1;var n=this._eventHandlers[e];return n||(n=this._eventHandlers[e]=[]),!(n.indexOf(t)>=0||(t._once=!1,n.push(t),0))},e.prototype.once=function(e,t){if(!e||!t)return!1;if(!this.isValidType(e))return!1;if(!this.isValidHandler(t))return!1;var n=this.on(e,t);return n&&(t._once=!0),n},e.prototype.off=function(e,t){if(!e)return this.offAll();if(t){if(this.isValidType(e)&&this.isValidHandler(t)){var n=this._eventHandlers[e];if(n&&n.length)for(var o=0;o<n.length;o++)if(n[o]===t){n.splice(o,1);break}}}else this._eventHandlers[e]=[]},e.prototype.offAll=function(){this._eventHandlers={}},e.prototype.fire=function(e,t){if(e&&this.isValidType(e)){var n=this._eventHandlers[e];if(n&&n.length)for(var o=this.createEvent(e,t),i=0,r=n;i<r.length;i++){var s=r[i];this.isValidHandler(s)&&(s._once&&(o.once=!0),s(o),o.once&&this.off(e,s))}}},e.prototype.has=function(e,t){if(!e||!this.isValidType(e))return!1;var n=this._eventHandlers[e];return!(!n||!n.length||t&&this.isValidHandler(t)&&!(n.indexOf(t)>=0))},e.prototype.getHandlers=function(e){return e&&this.isValidType(e)&&this._eventHandlers[e]||[]},e.prototype.createEvent=function(e,t,n){return void 0===n&&(n=!1),{type:e,data:t,timestamp:Date.now(),once:n}},e}();t.vp=n,new n}},t={};function n(o){var i=t[o];if(void 0!==i)return i.exports;var r=t[o]={exports:{}};return e[o](r,r.exports,n),r.exports}!function(){var e=Object.defineProperty,t=Object.defineProperties,o=Object.getOwnPropertyDescriptors,i=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,s=Object.prototype.propertyIsEnumerable,l=(t,n,o)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[n]=o,c=(e,t)=>{for(var n in t||(t={}))r.call(t,n)&&l(e,n,t[n]);if(i)for(var n of i(t))s.call(t,n)&&l(e,n,t[n]);return e},a=(e,n)=>t(e,o(n)),d=(e,t,n)=>new Promise(((o,i)=>{var r=e=>{try{l(n.next(e))}catch(e){i(e)}},s=e=>{try{l(n.throw(e))}catch(e){i(e)}},l=e=>e.done?o(e.value):Promise.resolve(e.value).then(r,s);l((n=n.apply(e,t)).next())}));function u(e,t,n){return Math.max(Math.min(e,n),t)}function m(){try{return unsafeWindow}catch(e){return window}}function y(e,t){var n;return null==(n=e.parentNode)||n.insertBefore(t,e.nextSibling),t}function p(e,t){let n=e.parentNode;if(!n)throw new Error("Element doesn't have a parent node");return n.replaceChild(t,e),t.appendChild(e),t}function f(e){let t=document.createElement("a");Object.assign(t,{className:"userutils-open-in-new-tab",target:"_blank",rel:"noopener noreferrer",href:e}),t.style.display="none",document.body.appendChild(t),t.click(),setTimeout(t.remove,50)}function h(e,t){return(Array.isArray(t)||t instanceof NodeList)&&(t=t.length),`${e}${1===t?"":"s"}`}function g(e){return new Promise((t=>{setTimeout(t,e)}))}function v(e){return d(this,arguments,(function*(e,t={}){let{timeout:n=1e4}=t,o=new AbortController,i=setTimeout((()=>o.abort()),n),r=yield fetch(e,a(c({},t),{signal:o.signal}));return clearTimeout(i),r}))}var b=new Map;function w(e,t){let n=[];b.has(e)&&(n=b.get(e)),n.push(t),b.set(e,n),E(e,n)}function E(e,t){let n=[];if(t.forEach(((t,o)=>{try{let i=t.all?document.querySelectorAll(e):document.querySelector(e);(null!==i&&i instanceof NodeList&&i.length>0||null!==i)&&(t.listener(i),t.continuous||n.push(o))}catch(t){console.error(`Couldn't call listener for selector '${e}'`,t)}})),n.length>0){let o=t.filter(((e,t)=>!n.includes(t)));0===o.length?b.delete(e):b.set(e,o)}}const x="production",C="main",S=x.match(/^{{.+}}$/)?"production":x,k=C.match(/^{{.+}}$/)?"main":C,L="production"===S?1:0,$={name:GM.info.script.name,version:GM.info.script.version,namespace:GM.info.script.namespace,lastCommit:"a48856c"};let q=1;function T(e){return"number"==typeof e.at(-1)?u(e.splice(e.length-1)[0],0,1):0}const A=`[${$.name}]`,N=`[${$.name}/#DEBUG]`;function O(...e){q<=T(e)&&console.log(A,...e)}function _(...e){q<=T(e)&&console.info(A,...e)}function M(...e){q<=T(e)&&console.warn(A,...e)}function P(...e){console.error(A,...e)}function R(){const{hostname:e}=new URL(location.href);if(e.includes("music.youtube"))return"ytm";if(e.includes("youtube"))return"yt";throw new Error("BetterYTM is running on an unexpected website. Please don't tamper with the @match directives in the userscript header.")}function j(e){return`https://raw.githubusercontent.com/Sv443/BetterYTM/${k}/assets/${e}`}var U=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))};function D(e){var t,n;if(["ArrowLeft","ArrowRight"].includes(e.code)){if(["INPUT","TEXTAREA","SELECT"].includes(null!==(n=null===(t=document.activeElement)||void 0===t?void 0:t.tagName)&&void 0!==n?n:"_"))return _(`Captured valid key but the current active element is <${document.activeElement.tagName.toLowerCase()}>, so the keypress is ignored`);O(`Captured key '${e.code}' in proxy listener`);const o={altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,target:document.body,currentTarget:document.body,originalTarget:document.body,explicitOriginalTarget:document.body,srcElement:document.body,type:"keydown",bubbles:!0,cancelBubble:!1,cancelable:!0,isTrusted:!0,repeat:!1,view:m()};let i=!1,r={};switch(e.code){case"ArrowLeft":r={code:"KeyH",key:"h",keyCode:72,which:72};break;case"ArrowRight":r={code:"KeyL",key:"l",keyCode:76,which:76};break;default:i=!0}if(i)M(`Captured key '${e.code}' has no defined behavior`);else{const t=Object.assign(Object.assign({code:""},o),r);document.body.dispatchEvent(new KeyboardEvent("keydown",t)),O(`Dispatched proxy keydown event: [${e.code}] -> [${t.code}]`)}}}function B(e){document.addEventListener("keydown",(t=>{"F9"===t.key&&function(e){var t;U(this,void 0,void 0,(function*(){try{if("ytm"===e&&!location.href.includes("/watch"))return M("Not on a video page, so the site switch is ignored");let n;if("ytm"===e?n="music":"yt"===e&&(n="www"),!n)throw new Error(`Unrecognized domain '${e}'`);H();const{pathname:o,search:i,hash:r}=new URL(location.href),s=null!==(t=yield new Promise((e=>{const t=R();try{if("ytm"===t){const t=document.querySelector("#progress-bar");return e(isNaN(Number(t.value))?null:Number(t.value))}if("yt"===t){!function(){const e=document.querySelector("#movie_player");if(!e)return!1;const t={view:m(),bubbles:!0,cancelable:!1};e.dispatchEvent(new MouseEvent("mouseenter",t));const{x:n,y:o,width:i,height:r}=e.getBoundingClientRect(),s=Math.round(o+r/2),l=n+Math.min(50,Math.round(i/3));e.dispatchEvent(new MouseEvent("mousemove",Object.assign(Object.assign({},t),{screenY:s,screenX:l,movementX:5,movementY:0})))}();const t='.ytp-chrome-bottom div.ytp-progress-bar[role="slider"]',n=document.querySelector(t);let o=n?Number(n.getAttribute("aria-valuenow")):-1;const i=new MutationObserver((()=>{o=Number(document.querySelector(t).getAttribute("aria-valuenow"))})),r=t=>{i.observe(t,{attributes:!0,attributeFilter:["aria-valuenow"]}),setTimeout((()=>{e(o>=0&&!isNaN(o)?o:null)}),500)};return n?r(n):w(t,{listener:r})}}catch(t){P("Couldn't get video time due to error:",t),e(null)}})))&&void 0!==t?t:0;O(`Found video time of ${s} seconds`);const l=i.split("&").filter((e=>!e.match(/^\??t=/))).join("&"),c=`https://${n}.youtube.com${o}${l.includes("?")?`${l.startsWith("?")?l:"?"+l}&t=${s}`:`?t=${s}`}${r}`;_(`Switching to domain '${e}' at ${c}`),location.assign(c)}catch(e){P("Error while switching site:",e)}}))}("yt"===e?"ytm":"yt")})),O("Initialized site switch listener")}let I=!0;function H(){I=!1,_("Disabled popup before leaving the site")}var V=n(546),G=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))};const K=new V.vp;function Y(e){return e.data}let z=[];var F=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))};let W=!1;function J(e){if(!W)return;W=!1,(null==e?void 0:e.bubbles)&&e.stopPropagation(),document.body.classList.remove("bytm-disable-scroll");const t=document.querySelector("#betterytm-menu-bg");t.style.visibility="hidden",t.style.display="none"}function X(){if(W)return;W=!0,document.body.classList.add("bytm-disable-scroll");const e=document.querySelector("#betterytm-menu-bg");e.style.visibility="visible",e.style.display="block"}var Q=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))},Z=function(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t,n=e[Symbol.asyncIterator];return n?n.call(e):(e="function"==typeof __values?__values(e):e[Symbol.iterator](),t={},o("next"),o("throw"),o("return"),t[Symbol.asyncIterator]=function(){return this},t);function o(n){t[n]=e[n]&&function(t){return new Promise((function(o,i){!function(e,t,n,o){Promise.resolve(o).then((function(t){e({value:t,done:n})}),t)}(o,i,(t=e[n](t)).done,t.value)}))}}};const ee="https://api.sv443.net/geniurl",te=`${ee}/search/top`,ne=30,oe=`&threshold=${u(.55,0,1)}`,ie=new Map,re=100;function se(e,t){return ie.get(`${e} - ${t}`)}let le="";function ce(e){const t=document.querySelector(".content-info-wrapper > yt-formatted-string");(()=>{Q(this,void 0,void 0,(function*(){const t=yield ue(),n=ye(null!=t?t:void 0);n.id="betterytm-lyrics-button",O("Inserted lyrics button into media controls bar"),y(e,n)}))})(),le=t.title,new MutationObserver((e=>{var t,n,o;return Q(this,void 0,void 0,(function*(){var i,r,s,l;try{for(t=!0,n=Z(e);!(i=(o=yield n.next()).done);){l=o.value,t=!1;try{const e=l.target.title;if(e!==le&&e.length>0){const t=document.querySelector("#betterytm-lyrics-button");if(!t)return;_(`Song title changed from '${le}' to '${e}'`),t.style.cursor="wait",t.style.pointerEvents="none";const n=t.querySelector("img");n.src=j("spinner.svg"),n.classList.add("bytm-spinner"),le=e;const o=yield ue();if(n.src=j("lyrics.svg"),n.classList.remove("bytm-spinner"),!o)continue;t.href=o,t.title="Open the current song's lyrics in a new tab",t.style.cursor="pointer",t.style.visibility="initial",t.style.display="inline-flex",t.style.pointerEvents="initial"}}finally{t=!0}}}catch(e){r={error:e}}finally{try{t||i||!(s=n.return)||(yield s.call(n))}finally{if(r)throw r.error}}}))})).observe(t,{attributes:!0,attributeFilter:["title"]})}function ae(e){return e.replace(/\(.+\)/gim,"").replace(/\[.+\]/gim,"").trim()}function de(e){return(e=e.split(/\s*\u2022\s*/gimu)[0]).match(/&/)&&(e=e.split(/\s*&\s*/gm)[0]),e.match(/,/)&&(e=e.split(/,\s*/gm)[0]),e.trim()}function ue(){var e;return Q(this,void 0,void 0,(function*(){try{const t="string"==typeof(null===(e=document.querySelector("ytmusic-player"))||void 0===e?void 0:e.getAttribute("video-mode_")),n=document.querySelector(".content-info-wrapper > yt-formatted-string"),o=document.querySelector("span.subtitle > yt-formatted-string:first-child");if(!n||!o||!n.title)return;const i=ae(n.title),r=de(o.title),s=()=>Q(this,void 0,void 0,(function*(){if(!i.includes("-"))return yield me(r,i);const[e,...t]=i.split("-").map((e=>e.trim()));return yield me(e,t.join(" "))}));return t?yield s():yield me(r,i)}catch(e){return void P("Couldn't resolve lyrics URL:",e)}}))}function me(e,t){var n,o,i;return Q(this,void 0,void 0,(function*(){try{const r=se(e,t);if(r)return _(`Found lyrics URL in cache: ${r}`),r;const s=Date.now(),l=`${te}?artist=${encodeURIComponent(e)}&song=${encodeURIComponent(t)}${oe}`;O(`Requesting URL from geniURL at '${l}'`);const c=yield v(l);if(429===c.status)return void alert(`You are being rate limited.\nPlease wait ${null!==(n=c.headers.get("retry-after"))&&void 0!==n?n:ne} seconds before requesting more lyrics.`);if(c.status<200||c.status>=300)return void P(`Couldn't fetch lyrics URL from geniURL - status: ${c.status} - response: ${null!==(i=null!==(o=(yield c.json()).message)&&void 0!==o?o:yield c.text())&&void 0!==i?i:"(none)"}`);const a=yield c.json();if("object"==typeof a&&a.error)return void P("Couldn't fetch lyrics URL:",a.message);const d=a.url;return _(`Found lyrics URL (after ${Date.now()-s}ms): ${d}`),function(e,t,n){ie.set(`${de(e)} - ${ae(t)}`,n),ie.size>re&&ie.delete([...ie.keys()].at(-1))}(e,t,d),d}catch(e){return void P("Couldn't get lyrics URL due to error:",e)}}))}function ye(e,t=!0){const n=document.createElement("a");n.className="ytmusic-player-bar bytm-generic-btn",n.title=e?"Click to open this song's lyrics in a new tab":"Loading lyrics URL...",e&&(n.href=e),n.role="button",n.target="_blank",n.rel="noopener noreferrer",n.style.visibility=t&&e?"initial":"hidden",n.style.display=t&&e?"inline-flex":"none";const o=document.createElement("img");return o.className="bytm-generic-btn-img",o.src=j("lyrics.svg"),n.appendChild(o),n}var pe=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))};let fe,he=0,ge=!1;function ve(){w(".bytm-mod-logo",{listener:e=>{if(e.classList.contains("bytm-logo-exchanged"))return;ge=!0,e.classList.add("bytm-logo-exchanged");const t=document.createElement("img");t.className="bytm-mod-logo-img",t.src=j("icon/icon.png"),e.insertBefore(t,e.querySelector("svg")),document.head.querySelectorAll('link[rel="icon"]').forEach((e=>{e.href=j("icon/icon.png")})),setTimeout((()=>{e.querySelectorAll(".bytm-mod-logo-ellipse").forEach((e=>e.remove()))}),1e3)}})}function be(e){const t=document.createElement("a");t.role="button",t.className="bytm-cfg-menu-option bytm-anchor",t.ariaLabel="Click to open BetterYTM's configuration menu";const n=document.createElement("div");n.className="bytm-cfg-menu-option-item",n.addEventListener("click",(e=>{const t=document.querySelector("ytmusic-nav-bar ytmusic-settings-button tp-yt-paper-icon-button");null==t||t.click(),he++,e.shiftKey&&!ge||5===he||X(),(!ge&&e.shiftKey||5===he)&&ve()}));const o=document.createElement("img");o.className="bytm-cfg-menu-option-icon",o.src=j("icon/icon.png");const i=document.createElement("div");i.className="bytm-cfg-menu-option-text",i.innerText="BetterYTM Configuration",n.appendChild(o),n.appendChild(i),t.appendChild(n),e.appendChild(t),O("Added BYTM-Configuration button to menu popover",t)}function we(e){return pe(this,void 0,void 0,(function*(){const t=document.createElement("div");t.className="bytm-queue-btn-container";const n=e.querySelector(".song-info");if(!n)return!1;const[o,i]=n.querySelectorAll("yt-formatted-string"),r=o.innerText,s=i.innerText;if(!r||!s)return!1;const l=ye(void 0,!1);l.title="Open this song's lyrics in a new tab",l.style.display="inline-flex",l.style.visibility="initial",l.style.pointerEvents="initial",l.addEventListener("click",(e=>pe(this,void 0,void 0,(function*(){let t;e.stopPropagation();const o=de(s),i=ae(r),c=se(o,i);if(c)t=c;else if(!n.hasAttribute("data-bytm-loading")){const e=l.querySelector("img");c||(n.setAttribute("data-bytm-loading",""),e.src=j("spinner.svg"),e.classList.add("bytm-spinner")),t=null!=c?c:yield me(o,i);const r=()=>{e.src=j("lyrics.svg"),e.classList.remove("bytm-spinner")};if(c||(n.removeAttribute("data-bytm-loading"),setTimeout(r,100)),!t)return r(),void(confirm("Couldn't find a lyrics page for this song.\nDo you want to open genius.com to manually search for it?")&&f("https://genius.com/search"))}t&&f(t)}))));const c=document.createElement("a");{Object.assign(c,{title:"Remove this song from the queue",className:"ytmusic-player-bar bytm-delete-from-queue bytm-generic-btn",role:"button"}),c.style.visibility="initial",c.addEventListener("click",(t=>pe(this,void 0,void 0,(function*(){t.stopPropagation();let n=document.querySelector("ytmusic-app ytmusic-popup-container tp-yt-iron-dropdown");try{const t=e.querySelector("ytmusic-menu-renderer yt-button-shape button");n&&n.setAttribute("data-bytm-hidden","true"),t.click(),yield g(25),n=document.querySelector("ytmusic-app ytmusic-popup-container tp-yt-iron-dropdown"),n.hasAttribute("data-bytm-hidden")||n.setAttribute("data-bytm-hidden","true");const o=n.querySelector("tp-yt-paper-listbox *[role=option]:nth-child(7)");yield g(20),o.click()}catch(e){P("Couldn't remove song from queue due to error:",e)}finally{null==n||n.removeAttribute("data-bytm-hidden")}}))));const t=document.createElement("img");t.className="bytm-generic-btn-img",t.src=j("delete.svg"),c.appendChild(t)}return t.appendChild(l),t.appendChild(c),n.appendChild(t),e.classList.add("bytm-has-queue-btns"),!0}))}const Ee=["/","/explore","/library"],xe={arrowKeySupport:{desc:"Arrow keys skip forwards and backwards by 10 seconds",type:"toggle",category:"input",default:!0},switchBetweenSites:{desc:"Add F9 as a hotkey to switch between the YT and YTM sites on a video / song",type:"toggle",category:"input",default:!0},switchSitesHotkey:{desc:"TODO(v1.1): Which hotkey needs to be pressed to switch sites?",type:"hotkey",category:"input",default:{key:"F9",shift:!1,ctrl:!1,meta:!1},visible:!1},disableBeforeUnloadPopup:{desc:"Completely disable the popup that sometimes appears before leaving the site",type:"toggle",category:"input",default:!1},anchorImprovements:{desc:"Add link elements all over the page so stuff can be opened in a new tab easier",type:"toggle",category:"input",default:!0},removeUpgradeTab:{desc:'Remove the "Upgrade" / YT Music Premium tab',type:"toggle",category:"layout",default:!0},volumeSliderLabel:{desc:"Add a percentage label to the volume slider",type:"toggle",category:"layout",default:!0},volumeSliderSize:{desc:"Width of the volume slider in pixels",type:"number",category:"layout",min:50,max:500,step:5,default:160,unit:"px"},volumeSliderStep:{desc:"Volume slider sensitivity - the smaller this number, the finer the volume control",type:"slider",category:"layout",min:1,max:20,default:2},watermarkEnabled:{desc:`Show a ${$.name} watermark under the YTM logo`,type:"toggle",category:"layout",default:!0},queueButtons:{desc:"Add buttons to each song in the queue to quickly open their lyrics or remove them from the queue",type:"toggle",category:"layout",default:!0},geniusLyrics:{desc:"Add a button to the media controls of the currently playing song to open its lyrics on genius.com",type:"toggle",category:"lyrics",default:!0}};var Ce=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))};const Se=1,ke=Object.keys(xe).reduce(((e,t)=>(e[t]=xe[t].default,e)),{});let Le;function $e(e=!1){return Ce(this,void 0,void 0,(function*(){return Le&&!e||(Le=yield qe()),Le}))}function qe(){return Ce(this,void 0,void 0,(function*(){const e=Object.assign({},ke);try{const t=yield GM.getValue("betterytm-config");return"string"!=typeof t||t.length<=3?(yield Te(),Le=e):Le=Object.assign(Object.assign({},e),t?JSON.parse(t):{})}catch(t){return P("Error loading feature configuration, resetting to default:",t),yield Te(),Le=e}}))}function Te(){return Le=Object.assign({},ke),GM.setValue("betterytm-config-ver",Se),GM.setValue("betterytm-config",JSON.stringify(ke))}var Ae=function(e,t,n,o){return new(n||(n=Promise))((function(i,r){function s(e){try{c(o.next(e))}catch(e){r(e)}}function l(e){try{c(o.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,l)}c((o=o.apply(e,t||[])).next())}))};const Ne=[j("icon/icon.png"),j("spinner.svg"),j("delete.svg"),j("lyrics.svg")];{const e="background: rgba(165, 38, 38, 1); background: linear-gradient(90deg, rgb(154, 31, 103) 0%, rgb(135, 31, 31) 40%, rgb(184, 64, 41) 100%);",t="color: #fff; font-size: 1.35em; padding-left: 6px; padding-right: 6px;";console.log(),console.log(`%c${$.name}%cv${$.version}%c\n\nBuild #${$.lastCommit} ─ ${$.namespace}`,`font-weight: bold; ${t} ${e}`,`background-color: #333; ${t}`,"padding: initial;"),console.log(["Powered by:","─ lots of ambition",`─ my song metadata API: ${ee}`,"─ my userscript utility library: https://github.com/Sv443-Network/UserUtils","─ this tiny event listener library: https://github.com/billjs/event-emitter"].join("\n")),console.log()}const Oe=R();function _e(){return Ae(this,void 0,void 0,(function*(){!function(e){let t=document.createElement("style");t.innerHTML=/* BetterYTM - global style */`:root{--bytm-menu-bg:#212121}#betterytm-menu-bg{background-color:rgba(0,0,0,.6);display:block;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:15}#betterytm-menu{background-color:var(--bytm-menu-bg);color:#fff;display:inline-block;height:auto;left:25vw;padding:10px 25px;position:fixed;top:10vh;width:50vw;z-index:16}#betterytm-menu-opts{max-height:70vh;overflow:auto}#betterytm-menu-titlecont{display:flex}#betterytm-menu-title{font-size:20px;margin-bottom:8px;margin-top:5px}#betterytm-menu-linkscont{display:flex}.betterytm-menu-link{cursor:pointer;display:inline-block}#betterytm-menu-close{cursor:pointer}.betterytm-ftconf-label{user-select:none}body.bytm-disable-scroll{overflow-y:hidden!important}.bytm-generic-btn{align-items:center;background-color:transparent;border-radius:100%;cursor:pointer;display:inline-flex;height:40px;justify-content:center;margin-left:8px;position:relative;vertical-align:middle;width:40px}.bytm-generic-btn:hover{background-color:var(--yt-spec-10-percent-layer,#1d1d1d)}.bytm-generic-btn-img{display:inline-block;height:24px;padding:5px;width:24px;z-index:10}.bytm-spinner{animation:rotate 1.5s linear infinite}@keyframes rotate{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.bytm-anchor{all:unset;cursor:pointer}ytmusic-logo a.bytm-logo-exchanged .bytm-mod-logo-path{animation:rotate 1s ease-in-out;transform-origin:12px 12px}ytmusic-logo a.bytm-logo-exchanged .bytm-mod-logo-img{animation:rotate-fade-in 1s ease-in-out;height:24px;position:absolute;width:24px;z-index:1000}@keyframes rotate-fade-in{0%{opacity:0;transform:rotate(0deg)}30%{opacity:0}90%{opacity:1}to{transform:rotate(1turn)}}.bytm-no-select{user-select:none;-ms-user-select:none;-moz-user-select:none;-webkit-user-select:none}.bytm-cfg-menu-option{display:block;padding:8px 0}.bytm-cfg-menu-option-item{align-items:center;cursor:pointer;display:flex;flex-direction:row;font-size:16px;font-weight:400;line-height:24px;min-height:var(--paper-item-min-height,40px);padding:var(--yt-compact-link-paper-item-padding,0 36px 0 16px);white-space:nowrap}.bytm-cfg-menu-option-item:hover{background-color:var(--yt-spec-badge-chip-background,#3e3e3e)}.bytm-cfg-menu-option-icon{align-items:center;display:flex;flex:none;flex-direction:row;height:24px;margin-right:16px;width:24px}.bytm-cfg-menu-option-text{font-size:1.4rem;line-height:2rem}yt-multi-page-menu-section-renderer.ytd-multi-page-menu-renderer{border-bottom:1px solid var(--yt-spec-10-percent-layer,#3e3e3e)}#bytm-watermark{color:#fff;cursor:pointer;display:inline-block;font-size:10px;left:97px;position:absolute;text-decoration:none;top:46px;z-index:10}#bytm-watermark:hover{text-decoration:underline}.side-panel.modular ytmusic-player-queue-item .song-info.ytmusic-player-queue-item{position:relative}.side-panel.modular ytmusic-player-queue-item .bytm-queue-btn-container{background:#000;background:linear-gradient(90deg,transparent,#000 15%);display:none;height:100%;padding-left:25px;position:absolute;right:0}.side-panel.modular ytmusic-player-queue-item:hover .bytm-queue-btn-container{display:inline-block}.side-panel.modular ytmusic-player-queue-item[play-button-state=loading] .bytm-queue-btn-container,.side-panel.modular ytmusic-player-queue-item[play-button-state=paused] .bytm-queue-btn-container,.side-panel.modular ytmusic-player-queue-item[play-button-state=playing] .bytm-queue-btn-container{background:linear-gradient(90deg,transparent,#1d1d1d 15%)}ytmusic-app ytmusic-popup-container tp-yt-iron-dropdown[data-bytm-hidden=true]{display:none!important}ytmusic-responsive-list-item-renderer .left-items{margin-right:0!important}.bytm-carousel-shelf-anchor{margin:0 var(--ytmusic-responsive-list-item-thumbnail-margin-right,16px) 0 0}#bytm-menu-header-container{align-items:center;border-color:#fff;border-style:none solid none none;display:flex;justify-content:flex-start}.bytm-menu-header-option{align-items:center;border-color:#fff;border-style:solid none;display:"flex";justify-content:center}#bytm-menu-header-option h3{margin:0}.bytm-menu-tab[data-active=false],.bytm-menu-tab[data-active=true]{display:none}`,document.head.appendChild(t)}(),function(e={}){new MutationObserver((()=>{for(let[e,t]of b.entries())E(e,t)})).observe(document.body,c({subtree:!0,childList:!0},e))}();const e=yield qe();O(`Initializing features for domain "${Oe}"...`);try{if("ytm"===Oe){try{!function(){var e,t;F(this,void 0,void 0,(function*(){const n=document.createElement("div");n.id="betterytm-menu-bg",n.title="Click here to close the menu",n.style.visibility="hidden",n.style.display="none",n.addEventListener("click",(e=>{var t;W&&"betterytm-menu-bg"===(null===(t=e.target)||void 0===t?void 0:t.id)&&J(e)})),document.body.addEventListener("keydown",(e=>{W&&"Escape"===e.key&&J(e)}));const o=document.createElement("div");o.title="",o.id="betterytm-menu",o.style.borderRadius="15px",o.style.display="flex",o.style.flexDirection="column",o.style.justifyContent="space-between";const i=document.createElement("div");i.style.padding="8px 20px 15px 20px",i.style.display="flex",i.style.justifyContent="space-between",i.id="betterytm-menu-titlecont";const r=document.createElement("h2");r.id="betterytm-menu-title",r.classList.add("bytm-no-select"),r.innerText=`${$.name} - Configuration`;const s=document.createElement("div");s.id="betterytm-menu-linkscont";const l=(e,t,n)=>{const o=document.createElement("a");o.className="betterytm-menu-link",o.rel="noopener noreferrer",o.target="_blank",o.href=t,o.title=n,o.style.marginLeft="10px";const i=document.createElement("img");i.className="betterytm-menu-img bytm-no-select",i.src=e,i.style.width="32px",i.style.height="32px",o.appendChild(i),s.appendChild(o)};l(j("external/github.png"),$.namespace,`${$.name} on GitHub`),l(j("external/greasyfork.png"),"https://greasyfork.org/xyz",`${$.name} on GreasyFork`);const c=document.createElement("img");c.id="betterytm-menu-close",c.src=j("close.png"),c.title="Click to close the menu",c.style.marginLeft="50px",c.style.width="32px",c.style.height="32px",c.addEventListener("click",J),s.appendChild(c),i.appendChild(r),i.appendChild(s);const a=document.createElement("div");a.id="betterytm-menu-opts",a.style.display="flex",a.style.flexDirection="column",a.style.overflowY="auto";const d=function(e,t=300){let n;return function(...o){clearTimeout(n),n=setTimeout((()=>e.apply(this,o)),t)}}(((e,t,n)=>F(this,void 0,void 0,(function*(){const o=e=>"object"==typeof e?JSON.stringify(e):String(e);_(`Feature config changed, key '${e}' from value '${o(t)}' to '${o(n)}'`);const i=Object.assign({},yield $e());i[e]=n,yield function(e){if(!e||"object"!=typeof e)throw new TypeError("Feature config not provided or invalid");return O("Saving new feature config:",e),Le=Object.assign({},e),GM.setValue("betterytm-config-ver",Se),GM.setValue("betterytm-config",JSON.stringify(e))}(i),O("Saved feature config changes:\n",yield GM.getValue("betterytm-config"))})))),u=yield $e();for(const n in u){const o=xe[n];if(!o||!1===o.visible)continue;const{desc:i,type:r,default:s}=o,l=null!==(e=null==o?void 0:o.step)&&void 0!==e?e:void 0,c=u[n],m=null!==(t=null!=c?c:s)&&void 0!==t?t:void 0,y=document.createElement("div");y.id=`betterytm-ftconf-${n}`,y.style.display="flex",y.style.flexDirection="row",y.style.justifyContent="space-between",y.style.padding="8px 20px";{const e=document.createElement("span");e.style.display="inline-block",e.style.fontSize="15px",e.innerText=i,y.appendChild(e)}{let e="text";switch(r){case"toggle":e="checkbox";break;case"slider":e="range";break;case"number":e="number"}const t=`betterytm-ftconf-${n}-input`,i=document.createElement("span");i.style.display="inline-block",i.style.whiteSpace="nowrap";const s=document.createElement("input");s.id=t,s.type=e,"toggle"===r&&(s.style.marginLeft="5px"),void 0!==m&&(s.value=String(m)),"number"===r&&l&&(s.step=l),o.min&&o.max&&(s.min=o.min,s.max=o.max),"toggle"===r&&void 0!==m&&(s.checked=Boolean(m));const c="string"==typeof o.unit?" "+o.unit:"",a=e=>String(e).trim(),u=e=>e?"On":"Off";let p;"slider"===r?(p=document.createElement("label"),p.classList.add("betterytm-ftconf-label"),p.style.marginRight="20px",p.style.fontSize="16px",p.htmlFor=t,p.innerText=a(m)+c,s.addEventListener("input",(()=>{p&&(p.innerText=a(parseInt(s.value))+c)}))):"toggle"===r&&(p=document.createElement("label"),p.classList.add("betterytm-ftconf-label"),p.style.paddingLeft="10px",p.style.paddingRight="5px",p.style.fontSize="16px",p.htmlFor=t,p.innerText=u(Boolean(m))+c,s.addEventListener("input",(()=>{p&&(p.innerText=u(s.checked)+c)}))),s.addEventListener("input",(()=>{let e=Number(String(s.value).trim());isNaN(e)&&(e=Number(s.value)),void 0!==m&&d(n,m,"toggle"!==r?e:s.checked)})),p&&i.appendChild(p),i.appendChild(s),y.appendChild(i)}a.appendChild(y)}const m=document.createElement("div");m.id="betterytm-menu-footer-cont",m.style.display="flex",m.style.flexDirection="row",m.style.justifyContent="space-between",m.style.padding="10px 20px",m.style.marginTop="20px",m.style.position="sticky",m.style.bottom="0",m.style.backgroundColor="var(--bytm-menu-bg)";const y=document.createElement("div");y.id="betterytm-menu-footer",y.style.fontSize="17px",y.style.textDecoration="underline",y.innerText="You need to reload the page to apply changes.";const p=document.createElement("button");p.style.marginLeft="20px",p.innerText="Reload now",p.title="Click to reload the page",p.addEventListener("click",(()=>location.reload()));const f=document.createElement("button");f.className="bytm-cfg-reset-btn",f.title="Click to reset all settings to their default value",f.innerText="Reset",f.addEventListener("click",(()=>F(this,void 0,void 0,(function*(){confirm("Do you really want to reset all settings to their default value?\nThe page will automatically reload if you proceed.")&&(yield Te(),location.reload())})))),y.appendChild(p),m.appendChild(y),m.appendChild(f),a.appendChild(m);const h=document.createElement("div");h.id="betterytm-menu-body",h.appendChild(i),h.appendChild(a);const g=document.createElement("div");g.style.display="flex",g.style.justifyContent="space-around",g.style.fontSize="1.15em",g.style.marginTop="10px",g.style.marginBottom="5px";const v=document.createElement("span");v.id="betterytm-menu-version",v.innerText=`v${$.version}`,g.appendChild(v),a.appendChild(g),o.appendChild(h),o.appendChild(g),n.appendChild(o),document.body.appendChild(n),O("Added menu element:",n)}))}()}catch(e){P("Couldn't add menu:",e)}(function(){G(this,void 0,void 0,(function*(){try{const e=new MutationObserver((([{addedNodes:e,removedNodes:t,target:n}])=>{(e.length>0||t.length>0)&&(_(`Detected queue change - added nodes: ${[...e.values()].length} - removed nodes: ${[...t.values()].length}`),K.fire("queueChanged",n))}));e.observe(document.querySelector(".side-panel.modular #contents.ytmusic-player-queue"),{childList:!0});const t=new MutationObserver((([{addedNodes:e,removedNodes:t,target:n}])=>{(e.length>0||t.length>0)&&(_(`Detected autoplay queue change - added nodes: ${[...e.values()].length} - removed nodes: ${[...t.values()].length}`),K.fire("autoplayQueueChanged",n))}));t.observe(document.querySelector(".side-panel.modular ytmusic-player-queue #automix-contents"),{childList:!0}),function(){var e;G(this,void 0,void 0,(function*(){let t;(null===(e=document.querySelector("ytmusic-browse-response#browse-page"))||void 0===e?void 0:e.hasAttribute("hidden"))&&(yield new Promise((e=>{t=setInterval((()=>{var n;(null===(n=document.querySelector("ytmusic-browse-response#browse-page"))||void 0===n?void 0:n.hasAttribute("hidden"))||(clearInterval(t),e())}),50)}))),K.fire("homePageLoaded"),_("Initialized home page observers");const n=new MutationObserver((([{addedNodes:e,removedNodes:t}])=>{(e.length>0||t.length>0)&&(_("Detected carousel shelf container change - added nodes:",e.length,"- removed nodes:",t.length),K.fire("carouselShelvesChanged",{addedNodes:e,removedNodes:t}))}));n.observe(document.querySelector("#contents.ytmusic-section-list-renderer"),{childList:!0}),z=z.concat([n])}))}(),_("Successfully initialized SiteEvents observers"),z=z.concat([e,t])}catch(e){P("Couldn't initialize SiteEvents observers due to an error:\n",e)}}))})(),w("tp-yt-iron-dropdown #contentWrapper ytd-multi-page-menu-renderer #container.menu-container",{listener:be}),e.arrowKeySupport&&(document.addEventListener("keydown",D),O("Added key press listener")),e.removeUpgradeTab&&(w("ytmusic-app-layout tp-yt-app-drawer #contentContainer #guide-content #items ytmusic-guide-entry-renderer:nth-child(4)",{listener:e=>{e.remove(),O("Removed large upgrade tab")}}),w("ytmusic-app-layout #mini-guide ytmusic-guide-renderer #sections ytmusic-guide-section-renderer[is-primary] #items ytmusic-guide-entry-renderer:nth-child(4)",{listener:e=>{e.remove(),O("Removed small upgrade tab")}})),e.watermarkEnabled&&function(){const e=document.createElement("a");e.role="button",e.id="bytm-watermark",e.className="style-scope ytmusic-nav-bar bytm-no-select",e.innerText=$.name,e.title="Open menu",e.tabIndex=1e3,function(){pe(this,void 0,void 0,(function*(){try{const e=yield v("https://music.youtube.com/img/on_platform_logo_dark.svg"),t=yield e.text();w("ytmusic-logo a",{listener:e=>{var n;e.classList.add("bytm-mod-logo","bytm-no-select"),e.innerHTML=t,e.querySelectorAll("ellipse").forEach((e=>{e.classList.add("bytm-mod-logo-ellipse")})),null===(n=e.querySelector("path"))||void 0===n||n.classList.add("bytm-mod-logo-path"),O("Swapped logo to inline SVG")}})}catch(e){P("Couldn't improve logo due to an error:",e)}}))}(),e.addEventListener("click",(e=>{e.stopPropagation(),he++,e.shiftKey&&!ge||5===he||X(),(!ge&&e.shiftKey||5===he)&&ve()})),e.addEventListener("keydown",(e=>{"Enter"===e.key&&(e.stopPropagation(),he++,e.shiftKey&&!ge||5===he||X(),(!ge&&e.shiftKey||5===he)&&ve())})),y(document.querySelector("#left-content"),e),O("Added watermark element",e)}(),e.geniusLyrics&&w(".middle-controls-buttons ytmusic-like-button-renderer#like-button-renderer",{listener:ce}),e.queueButtons&&function(){const e=e=>{let t=0;for(const n of Y(e).childNodes)n.classList.contains("bytm-has-queue-btns")||(we(n),t++);t>0&&O(`Added buttons to ${t} new queue ${h("item",t)}`)};K.on("queueChanged",e),K.on("autoplayQueueChanged",e);const t=document.querySelectorAll("#contents.ytmusic-player-queue > ytmusic-player-queue-item");0!==t.length&&(t.forEach((e=>we(e))),O(`Added buttons to ${t.length} existing queue ${h("item",t)}`))}(),e.anchorImprovements&&function(){try{const e=e=>{if(e.querySelector("ytmusic-responsive-list-item-renderer")){const t=e.querySelector("ul#items");if(t){const e=function(e){if(e.classList.contains("bytm-anchors-improved"))return 0;let t=0;try{const n=e.querySelectorAll("ytmusic-responsive-list-item-renderer");for(const e of n){const n=e.querySelector(".left-items"),o=e.querySelector(".title-column yt-formatted-string.title a");if(!n||!o){P("Couldn't add carousel shelf anchor improvements because either the thumbnail or title element couldn't be found");continue}const i=document.createElement("a");i.className="bytm-carousel-shelf-anchor bytm-anchor",i.href=o.href,i.target="_self",i.role="button",i.addEventListener("click",(e=>{e.preventDefault()})),p(n,i),t++}}catch(e){P("Couldn't add anchor improvements due to error:",e)}finally{e.classList.add("bytm-anchors-improved")}return t}(t);e>0&&O(`Added anchor improvements to ${e} carousel shelf ${h("item",e)}`)}}};w("ytmusic-carousel-shelf-renderer",{listener:()=>{document.body.querySelectorAll("ytmusic-carousel-shelf-renderer").forEach(e)}}),K.on("carouselShelvesChanged",(t=>{const{addedNodes:n,removedNodes:o}=Y(t);n.length>0&&n.forEach(e)}));const t=t=>{const n=null==t?void 0:t.querySelectorAll("ytmusic-carousel-shelf-renderer");n&&n.forEach(e)},n='ytmusic-section-list-renderer[page-type="MUSIC_PAGE_TYPE_TRACK_RELATED"] #contents';w('ytmusic-tab-renderer[page-type="MUSIC_PAGE_TYPE_TRACK_RELATED"]',{listener:e=>{new MutationObserver((([{addedNodes:e,removedNodes:o}])=>{(e.length>0||o.length>0)&&t(document.querySelector(n))})).observe(e,{childList:!0})}}),w(n,{listener:e=>{t(e)}})}catch(e){P("Couldn't improve carousel shelf anchors due to an error:",e)}try{const e=e=>{const t=e.parentNode.querySelectorAll("ytmusic-guide-entry-renderer tp-yt-paper-item");return t.forEach(((e,t)=>{var n;const o=document.createElement("a");o.classList.add("bytm-anchor","bytm-no-select"),o.role="button",o.target="_self",o.href=null!==(n=Ee[t])&&void 0!==n?n:"#",o.title="Middle click to open in a new tab",o.addEventListener("click",(e=>{e.preventDefault()})),p(e,o)})),t.length};w("ytmusic-app-layout tp-yt-app-drawer #contentContainer #guide-content #items ytmusic-guide-entry-renderer",{listener:t=>{const n=e(t);O(`Added anchors around ${n} sidebar ${h("item",n)}`)}}),w("ytmusic-app-layout #mini-guide ytmusic-guide-renderer ytmusic-guide-section-renderer #items ytmusic-guide-entry-renderer",{listener:t=>{const n=e(t);O(`Added anchors around ${n} mini sidebar ${h("item",n)}`)}})}catch(e){P("Couldn't add anchors to sidebar items due to an error:",e)}}(),setInterval((()=>{const e=b;!function(...e){console.log(N,...e)}("selector map",e.size,e)}),500)}["ytm","yt"].includes(Oe)&&e.switchBetweenSites&&B(Oe)}catch(e){P("General error while executing feature:",e)}}))}var Me;q=L,"ytm"===Oe&&(Error.stackTraceLimit=1e3,Me=window.__proto__.addEventListener,window.__proto__.addEventListener=function(...e){return I||"beforeunload"!==e[0]?Me.apply(this,e):O("Prevented beforeunload event listener from being called")},$e().then((e=>{e.disableBeforeUnloadPopup&&H()}))),function(){Ae(this,void 0,void 0,(function*(){"ytm"===Oe&&function(e,t=!1){let n=e.map((e=>new Promise(((n,o)=>{let i=new Image;i.src=e,i.addEventListener("load",(()=>n(i))),i.addEventListener("error",(e=>t&&o(e)))}))));return Promise.allSettled(n)}(Ne,!0).then((()=>O(`Preloaded ${Ne.length} ${h("image",Ne)}`))).catch((e=>P(`Couldn't preload images: ${e}`))),yield function(){return pe(this,void 0,void 0,(function*(){fe=yield $e()}))}();try{document.addEventListener("DOMContentLoaded",_e)}catch(e){P("General Error:",e)}}))}()}();