VGMdb Metadata copy

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();

})();