VGMdb Tracklist copy

Adds copy buttons to copy tracklists from VGMdb album pages.

// ==UserScript==
// @name         VGMdb Tracklist copy
// @namespace    https://vgmdb.net/
// @version      1.5
// @description  Adds copy buttons to copy tracklists from VGMdb album pages.
// @author       kahpaibe
// @match        https://vgmdb.net/album/*
// @grant        none
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function () {
    'use strict';
  
    const perDiscBtnTitleName = "⎘"; const perDiscBtnTitleName_pressed = "✔ COPIED"; const perDiscBtnTitleMO = "Copy titles for this disc";
    const perDiscBtnFullName = "⎘"; const perDiscBtnFullName_pressed = "✔ COPIED"; const perDiscBtnFullMO = "Copy tracklist for this disc";
    const perLangBtnTitleName = "⎘"; const perLangBtnTitleName_pressed = "✔ COPIED"; const perLangBtnTitleMO = "Copy titles of all discs";
    const perLangBtnFullName = "⎘"; const perLangBtnFullName_pressed = "✔ COPIED"; const perLangBtnFullMO = "Copy tracklists of all discs";
  
    const styleButton = (button) => {
      button.style.marginLeft = '8px';
      button.style.padding = '1px 6px';
      button.style.fontSize = '0.75em';
      button.style.color = '#CEFFFF';
      button.style.background = 'transparent';
      button.style.border = '1px solid #CEFFFF';
      button.style.cursor = 'pointer';
      button.style.transition = 'background 0.3s, color 0.3s, transform 0.2s ease-in-out';
      button.style.verticalAlign = 'middle';
  
      // Animation on hover and focus
      button.onmouseover = () => {
        button.style.background = '#CEFFFF';
        button.style.color = '#000000';
        button.style.transform = 'scale(1.05)';
      };
  
      button.onmouseout = () => {
        button.style.background = 'transparent';
        button.style.color = '#CEFFFF';
        button.style.transform = 'scale(1)';
      };
  
      // Animation when clicked
      button.onmousedown = () => {
        button.style.transform = 'scale(0.95)';
      };
  
      button.onmouseup = () => {
        button.style.transform = 'scale(1)';
      };
    };
  
  
    // === PER-DISC BUTTONS ===
    const addPerDiscButtons = () => {
      const tracklistContainer = document.querySelector('#tracklist');
      if (!tracklistContainer) return;
  
      const allSpans = tracklistContainer.querySelectorAll('span');
  
      allSpans.forEach(span => {
        if (!/Disc \d+/.test(span.textContent)) return;
  
        let sibling = span.nextElementSibling;
        while (sibling && sibling.tagName !== 'TABLE') {
          sibling = sibling.nextElementSibling;
        }
  
        if (!sibling) return;
        const trackTable = sibling;
  
        const btnPerDiscTitle = document.createElement('button');
        btnPerDiscTitle.innerText = perDiscBtnTitleName;
        btnPerDiscTitle.title = perDiscBtnTitleMO;
        styleButton(btnPerDiscTitle);
        btnPerDiscTitle.onclick = () => {
          const tracks = trackTable.querySelectorAll('tr'); // Corrected to target the right table rows
          const lines = [];
          for (const tr of tracks) {
            const tds = tr.querySelectorAll('td');
            if (tds.length >= 2) { // Ensure the row has at least two cells (name + optional duration)
              const title = tds[1].textContent.trim(); // Get the track name (second <td>)
              lines.push(title); // Add the name to the list
            }
          }
          if (lines.length > 0) {
            navigator.clipboard.writeText(lines.join('\n')).then(() => {
              btnPerDiscTitle.innerText = perDiscBtnTitleName_pressed;
              setTimeout(() => btnPerDiscTitle.innerText = perDiscBtnTitleName, 1500);
          });
          }
        };
  
        const btnPerDiscFull = document.createElement('button');
        btnPerDiscFull.innerText = perDiscBtnFullName;
        btnPerDiscFull.title = perDiscBtnFullMO;
        styleButton(btnPerDiscFull);
        btnPerDiscFull.onclick = () => {
          const tracks = trackTable.querySelectorAll('tr'); // Corrected to target the right table rows
          const lines = [];
          for (const tr of tracks) {
            const tds = tr.querySelectorAll('td');
            if (tds.length >= 3) { // Ensure the row has at least three cells (track number, name, duration)
              const number = tds[0].textContent.trim(); // Get the track number (first <td>)
              const title = tds[1].textContent.trim(); // Get the track name (second <td>)
              const duration = tds[2].textContent.trim(); // Get the track duration (third <td>)
              lines.push(`${number} ${title} ${duration}`); // Format and add to list
            }
          }
          if (lines.length > 0) {
            navigator.clipboard.writeText(lines.join('\n')).then(() => {
              btnPerDiscFull.innerText = perDiscBtnFullName_pressed;
              setTimeout(() => btnPerDiscFull.innerText = perDiscBtnFullName, 1500);
          });
          }
        };
  
        // Add buttons to the DOM next to the disc label
        span.appendChild(btnPerDiscTitle);
        span.appendChild(btnPerDiscFull);
      });
    };
  
    // === PER-LANGUAGE BUTTONS ===
    const addPerLanguageButtons = () => {
      const tabNav = document.querySelector('#tlnav');
      if (!tabNav) return;
  
      const tabLinks = tabNav.querySelectorAll('li');
  
      tabLinks.forEach(li => {
        const rel = li.querySelector('a')?.getAttribute('rel');
        if (!rel) return;
  
        const tlbox = document.getElementById(rel);
        if (!tlbox) return;
  
        // --- Names Only Button ---
        const btnPerLangTitle = document.createElement('button');
        btnPerLangTitle.innerText = perLangBtnTitleName;
        btnPerLangTitle.title = perLangBtnTitleMO;
        styleButton(btnPerLangTitle);
  
        btnPerLangTitle.addEventListener('click', () => {
          const spans = tlbox.querySelectorAll('span');
          let result = '';
  
          spans.forEach(span => {
            if (!/Disc \d+/.test(span.textContent)) return;
  
            let sibling = span.nextElementSibling;
            while (sibling && sibling.tagName !== 'TABLE') {
              sibling = sibling.nextElementSibling;
            }
            if (!sibling) return;
  
            const trackRows = sibling.querySelectorAll('tr.rolebit');
            if (trackRows.length === 0) return;
  
            const discTitle = span.childNodes[0]?.textContent.trim();  // Only the original text, not the button
            result += `${discTitle}:\n`;
            trackRows.forEach(row => {
              const titleCell = row.querySelectorAll('td')[1];
              const title = titleCell?.textContent.trim();
              result += `${title}\n`;
            });
            result += '\n';
          });
  
          navigator.clipboard.writeText(result.trim()).then(() => {
            btnPerLangTitle.innerText = perLangBtnTitleName_pressed;
            setTimeout(() => btnPerLangTitle.innerText = perLangBtnTitleName, 1500);
          });
        });
  
        // --- Full Info Button ---
        const btnPerLangFull = document.createElement('button');
        btnPerLangFull.innerText = perLangBtnFullName;
        btnPerLangFull.title = perLangBtnFullMO;
        styleButton(btnPerLangFull);
  
        btnPerLangFull.addEventListener('click', () => {
          const spans = tlbox.querySelectorAll('span');
          let result = '';
  
          spans.forEach(span => {
            if (!/Disc \d+/.test(span.textContent)) return;
  
            let sibling = span.nextElementSibling;
            while (sibling && sibling.tagName !== 'TABLE') {
              sibling = sibling.nextElementSibling;
            }
            if (!sibling) return;
  
            const trackRows = sibling.querySelectorAll('tr.rolebit');
            if (trackRows.length === 0) return;
  
            const discTitle = span.childNodes[0]?.textContent.trim();  // Only the original text, not the button
            result += `${discTitle}:\n`;
            trackRows.forEach(row => {
              const number = row.querySelector('td .label')?.textContent.trim();
              const title = row.querySelectorAll('td')[1]?.textContent.trim();
              const duration = row.querySelectorAll('td')[2]?.textContent.trim();
              result += `${number}. ${title} ${duration}\n`;
            });
            result += '\n';
          });
  
          navigator.clipboard.writeText(result.trim()).then(() => {
            btnPerLangFull.innerText = perLangBtnFullName_pressed;
            setTimeout(() => btnPerLangFull.innerText = perLangBtnFullName, 1500);
          });
        });
  
        li.appendChild(btnPerLangTitle);
        li.appendChild(btnPerLangFull);
      });
    };
  
    // Run after page loads
    addPerDiscButtons();
    addPerLanguageButtons();
  })();