VGMdb Metadata copy

Adds copy buttons to the VGMdb album pages to easily copy metadata.

// ==UserScript==
// @name         VGMdb Metadata copy
// @namespace    https://vgmdb.net/
// @version      1.8
// @description  Adds copy buttons to the VGMdb album pages to easily copy metadata.
// @author       kahpaibe
// @match        https://vgmdb.net/album/*
// @grant        none
// @run-at       document-end
// @license MIT
// ==/UserScript==

(function() {
  'use strict';

  const PREFIX = '';
  const SUFFIX = '';

  // Helper function to style buttons consistently
  const styleButton = (button) => {
    button.title = 'Copy this text to clipboard';
    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';
    button.style.verticalAlign = 'middle';
  };

  // Helper function to create and append a "COPY" button next to a field
  const addCopyButton = (innerText, labelText, selector, tooltipText, fieldValue) => {
    const rows = document.querySelectorAll('#album_infobit_large tr');

    rows.forEach(row => {
      const labelCell = row.querySelector('td span.label b');
      if (labelCell && labelCell.textContent.trim() === labelText) {
        const valueCell = row.cells[1];
        let field = fieldValue || valueCell.textContent.trim();
        if (selector) {
          const link = valueCell.querySelector(selector);
          if (link) {
            field = link.textContent.trim();
          }
        }

        const button = document.createElement('span');
        button.innerText = innerText;  // Set innerText here
        button.title = tooltipText;
        styleButton(button);

        button.onclick = () => {
          navigator.clipboard.writeText(field).then(() => {
            const original = button.innerText;
            button.innerText = '✔ COPIED';
            setTimeout(() => button.innerText = original, 1500);
          });
        };

        labelCell.parentElement.appendChild(button);
      }
    });
  };

  // Function to handle Release Date buttons
  const addReleaseDateButtons = () => {
    const rows = document.querySelectorAll('#album_infobit_large tr');
    const releaseDateRow = Array.from(rows).find(row => {
      const labelCell = row.querySelector('td span.label b');
      return labelCell && labelCell.textContent.trim() === 'Release Date';
    });

    if (!releaseDateRow) return;

    const valueCell = releaseDateRow.cells[1];
    const dateLink = valueCell.querySelector('a[title^="View albums released on"]');
    const eventLink = valueCell.querySelector('a.link_event');

    let formattedDate = '';
    if (dateLink) {
      const releaseDateStr = dateLink.textContent.trim();
      const dateObj = new Date(releaseDateStr);
      formattedDate = `${dateObj.getFullYear()}.${('0' + (dateObj.getMonth() + 1)).slice(-2)}.${('0' + dateObj.getDate()).slice(-2)}`;
      addCopyButton('⎘', 'Release Date', null, 'Copy Release Date to clipboard', formattedDate);
    }

    let event = '';
    if (eventLink) {
      event = eventLink.textContent.trim();
      addCopyButton('⎘', 'Release Date', null, 'Copy Event to clipboard', event);
    }

    if (dateLink) {
      const combined = event ? `[${formattedDate}][${event}]` : `[${formattedDate}]`;
      addCopyButton('⎘', 'Release Date', null, 'Copy formatted release date to clipboard', combined);
    }
  };

  const addTitleCopyButtons = () => {
    const visibleTitles = document.querySelectorAll('.albumtitle');

    visibleTitles.forEach(span => {
      const computedStyle = window.getComputedStyle(span);
      if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden') {
        return;
      }

      // Skip titles inside <a> tags (like in the album thumb list)
      if (span.closest('a')) {
        return;
      }

      const fragments = [];
      let currentText = '';

      // Break apart the nodes and <br> inside this span
      span.childNodes.forEach(node => {
        if (node.nodeName === 'BR') {
          if (currentText.trim()) fragments.push(currentText.trim());
          fragments.push('<br>');
          currentText = '';
        } else if (node.nodeType === Node.TEXT_NODE) {
          currentText += node.textContent;
        } else {
          currentText += node.outerHTML || node.textContent;
        }
      });

      if (currentText.trim()) {
        fragments.push(currentText.trim());
      }

      // Clear the span and re-inject each line with its own copy button
      span.innerHTML = '';

      fragments.forEach(fragment => {
        if (fragment === '<br>') {
          span.appendChild(document.createElement('br'));
          return;
        }

        const lineSpan = document.createElement('span');
        lineSpan.textContent = fragment;

        const button = document.createElement('button');
        styleButton(button);
        button.innerText = '⎘';  // Ensure button has text
        button.title = 'Copy this title to clipboard';

        button.addEventListener('click', () => {
          navigator.clipboard.writeText(fragment).then(() => {
            const original = button.innerText;
            button.innerText = '✔ COPIED!';
            setTimeout(() => button.innerText = original, 1000);
          });
        });

        span.appendChild(lineSpan);
        span.appendChild(button);

        // Add a second button for copying formatted string
        const formattedStringButton = document.createElement('button');
        styleButton(formattedStringButton);
        formattedStringButton.innerText = '⎘'; // Same button style
        formattedStringButton.title = 'Copy formatted album info to clipboard';

        // Fetch Publisher and Catalog Number dynamically within the context
        const publisherCell = Array.from(document.querySelectorAll('td span.label b')).find(b => b.textContent.trim() === 'Publisher');
        const publisher = publisherCell ? publisherCell.closest('tr').querySelector('td:nth-child(2)').textContent.trim() : '';

        const catalogNumberCell = Array.from(document.querySelectorAll('td span.label b')).find(b => b.textContent.trim() === 'Catalog Number');
        const catalogNumber = catalogNumberCell ? catalogNumberCell.closest('tr').querySelector('td:nth-child(2)').textContent.trim() : '';


        // Re-fetch Release Date and Event here to avoid assumptions
        const rows = document.querySelectorAll('#album_infobit_large tr');
        let formattedDate = '';
        let event = '';

        rows.forEach(row => {
          const labelCell = row.querySelector('td span.label b');
          if (labelCell && labelCell.textContent.trim() === 'Release Date') {
            const valueCell = row.cells[1];
            const dateLink = valueCell.querySelector('a[title^="View albums released on"]');
            const eventLink = valueCell.querySelector('a.link_event');

            if (dateLink) {
              const releaseDateStr = dateLink.textContent.trim();
              const dateObj = new Date(releaseDateStr);
              formattedDate = `${dateObj.getFullYear()}.${('0' + (dateObj.getMonth() + 1)).slice(-2)}.${('0' + dateObj.getDate()).slice(-2)}`;
            }

            if (eventLink) {
              event = eventLink.textContent.trim();
            }
          }
        });

        // Construct the formatted string
        let formattedString = PREFIX;
        formattedString += `[${formattedDate}]`;
        if (event) {
          formattedString += `[${event}]`;
        }
        if (publisher) {
          formattedString += ` ${publisher} -`;
        }
        formattedString += ` ${fragment}`;
        if (catalogNumber) {
          formattedString += ` {${catalogNumber}}`;
        } else {
          formattedString += ' {nocat#}';
        }
        formattedString += SUFFIX;


        formattedStringButton.addEventListener('click', () => {
          navigator.clipboard.writeText(formattedString).then(() => {
            const original = formattedStringButton.innerText;
            formattedStringButton.innerText = '✔ COPIED!';
            setTimeout(() => formattedStringButton.innerText = original, 1000);
          });
        });

        // Append button to the DOM (you can adjust where this button goes)
        document.body.appendChild(formattedStringButton);

        span.appendChild(formattedStringButton);

      });
    });
  };


  // Main function to initialize the copy buttons
  const initializeCopyButtons = () => {
    // Add metadata buttons
    addCopyButton('⎘', 'Catalog Number', null, 'Copy Catalog Number to clipboard');
    addCopyButton('⎘', 'Publisher', '.productname', 'Copy Publisher to clipboard');
    addReleaseDateButtons();  // Add the buttons for Release Date, Event, and Date+Event

    // Add inline copy buttons for album titles and secondary names
    addTitleCopyButtons();
  };

  // Initialize the script
  initializeCopyButtons();

})();