Add label, UPC, and release dates to Deezer album pages
当前为
// ==UserScript==
// @name Deezer Album Metadata Enhancer
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Add label, UPC, and release dates to Deezer album pages
// @author waiter7
// @match https://www.deezer.com/*/album/*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
obs.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
reject(new Error('Element not found within timeout'));
}, timeout);
});
}
function formatDate(dateString) {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
} catch (e) {
return dateString;
}
}
function extractAlbumId() {
const path = window.location.pathname;
const match = path.match(/\/album\/(\d+)/);
return match ? match[1] : null;
}
function addMetadata() {
try {
// Get the album data from the global state
const appState = window.__DZR_APP_STATE__;
if (!appState || !appState.DATA) {
console.log('No app state found, retrying...');
return false;
}
const albumData = appState.DATA;
const albumId = extractAlbumId();
if (!albumId) {
console.log('Could not extract album ID from URL');
return false;
}
// Extract metadata
const label = albumData.LABEL_NAME || 'N/A';
const upc = albumData.UPC || 'N/A';
const digitalReleaseDate = formatDate(albumData.DIGITAL_RELEASE_DATE);
const physicalReleaseDate = formatDate(albumData.PHYSICAL_RELEASE_DATE);
const originalReleaseDate = formatDate(albumData.ORIGINAL_RELEASE_DATE);
console.log('Album metadata:', {
label,
upc,
digitalReleaseDate,
physicalReleaseDate,
originalReleaseDate
});
// Find the existing metadata list (tracks/minutes/date/fans)
const metadataList = document.querySelector('ul.css-1s16397');
if (!metadataList) {
console.log('Metadata list not found');
return false;
}
// Check if our metadata is already added
if (document.querySelector('.custom-metadata-table')) {
console.log('Metadata already added');
return true;
}
// Create a clean table for metadata
const tableContainer = document.createElement('div');
tableContainer.className = 'custom-metadata-table';
tableContainer.style.cssText = `
margin-top: 16px;
background: #f8f9fa;
border-radius: 8px;
padding: 16px;
border: 1px solid #e9ecef;
`;
const table = document.createElement('table');
table.style.cssText = `
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: 13px;
color: #495057;
`;
// Create table header
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
headerRow.style.borderBottom = '2px solid #dee2e6';
const headers = ['Label', 'UPC', 'Digital Release', 'Physical Release', 'Original Release', 'API'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
th.style.cssText = `
text-align: left;
padding: 8px 12px;
font-weight: 600;
color: #343a40;
background: #e9ecef;
border-right: 1px solid #dee2e6;
`;
if (headerText === 'API') {
th.style.borderRight = 'none';
}
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Create table body
const tbody = document.createElement('tbody');
const dataRow = document.createElement('tr');
dataRow.style.backgroundColor = '#ffffff';
const values = [label, upc, digitalReleaseDate, physicalReleaseDate, originalReleaseDate];
// Add data cells
values.forEach((value, index) => {
const td = document.createElement('td');
td.textContent = value || 'N/A';
td.style.cssText = `
padding: 12px;
border-right: 1px solid #dee2e6;
vertical-align: top;
background: #ffffff;
`;
if (index === values.length - 1) {
td.style.borderRight = 'none';
}
dataRow.appendChild(td);
});
// Add API link cell
const apiTd = document.createElement('td');
apiTd.style.cssText = `
padding: 12px;
vertical-align: top;
background: #ffffff;
text-align: center;
`;
const apiLink = document.createElement('a');
apiLink.href = `https://api.deezer.com/album/${albumId}`;
apiLink.target = '_blank';
apiLink.textContent = 'View API';
apiLink.style.cssText = `
color: #a238ff;
text-decoration: none;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
background: #f8f5ff;
border: 1px solid #a238ff;
display: inline-block;
transition: all 0.2s ease;
`;
apiLink.addEventListener('mouseenter', () => {
apiLink.style.background = '#a238ff';
apiLink.style.color = '#ffffff';
});
apiLink.addEventListener('mouseleave', () => {
apiLink.style.background = '#f8f5ff';
apiLink.style.color = '#a238ff';
});
apiTd.appendChild(apiLink);
dataRow.appendChild(apiTd);
tbody.appendChild(dataRow);
table.appendChild(tbody);
tableContainer.appendChild(table);
// Insert the table after the existing metadata list
metadataList.parentNode.insertBefore(tableContainer, metadataList.nextSibling);
console.log('Metadata added successfully');
return true;
} catch (error) {
console.error('Error adding metadata:', error);
return false;
}
}
function init() {
console.log('Deezer Album Metadata Enhancer: Starting...');
// Wait for the page to load and the metadata list to be available
waitForElement('ul.css-1s16397')
.then(() => {
console.log('Metadata list found, attempting to add custom metadata...');
// Try to add metadata immediately
if (!addMetadata()) {
// If it fails, wait a bit for the app state to load
setTimeout(() => {
addMetadata();
}, 2000);
}
})
.catch(error => {
console.error('Failed to find metadata list:', error);
});
// Also listen for navigation changes (for SPA navigation)
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
if (url.includes('/album/')) {
console.log('Navigation detected, re-initializing...');
setTimeout(init, 1000);
}
}
}).observe(document, { subtree: true, childList: true });
}
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();