返回首頁 

Spotify Web - Download Cover

Adds a button to download the full size cover art from Spotify Web Player


Install this script?
  1. // ==UserScript==// @name Spotify Web - Download Cover// @name:es Spotify Web - Descargar portada// @name:pt Spotify Web - Descarregar capa// @name:pt-BR Spotify Web - Baixar capa// @name:it Spotify Web - Scarica copertina// @name:fr Spotify Web - Télécharger pochette// @name:de Spotify Web - Cover herunterladen// @name:ru Spotify Web - Скачать обложку// @name:zh-CN Spotify Web - 下载封面// @name:ja Spotify Web - カバーをダウンロード// @namespace http://tampermonkey.net/// @description Adds a button to download the full size cover art from Spotify Web Player// @description:es Agrega un botón para descargar la portada en tamaño completo del reproductor web de Spotify// @description:pt Adiciona um botão para baixar a capa em tamanho completo do reproductor web do Spotify// @description:pt-BR Adiciona um botão para baixar a capa em tamanho real do Spotify Web Player// @description:it Aggiunge un pulsante per scaricare la copertina a dimensione piena dal lettore web di Spotify// @description:fr Ajoute un bouton pour télécharger la pochette en taille réelle depuis le lecteur web Spotify// @description:de Fügt eine Schaltfläche zum Herunterladen des Covers in voller Größe vom Spotify Web Player hinzu// @description:ru Добавляет кнопку для скачивания обложки в полном размере из веб-плеера Spotify// @description:zh-CN 为Spotify网页播放器添加下载全尺寸封面艺术的按钮// @description:ja Spotify Web Playerからフルサイズのカバーアートをダウンロードするボタンを追加します// @version 1.6.1// @match https://open.spotify.com/*// @author Levi Somerset// @license MIT// @icon https://www.google.com/s2/favicons?sz=64&domain=spotify.com// @grant GM_download// @grant GM_xmlhttpRequest// @grant window.onurlchange// @connect i.scdn.co// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js// ==/UserScript==(function() {'use strict';const CONFIG = {COVER_BASE: 'https://i.scdn.co/image/',SIZES: ['ab67616d0000b273', 'ab67616d00001e02', 'ab67616d00004851'],FULL_SIZE: 'ab67616d000082c1',DEBOUNCE_DELAY: 300};const translations = {en: ['Download Cover', 'Cover downloaded: %s', 'Failed to download cover'],es: ['Descargar portada', 'Portada descargada: %s', 'No se pudo descargar la portada'],pt: ['Baixar capa', 'Capa baixada: %s', 'Falha ao baixar a capa'],"pt-BR": ['Baixar capa', 'Capa baixada: %s', 'Falha ao baixar a capa'],it: ['Scarica copertina', 'Copertina scaricata: %s', 'Impossibile scaricare la copertina'],fr: ['Télécharger pochette', 'Pochette téléchargée: %s', 'Échec du téléchargement de la pochette'],de: ['Cover herunterladen', 'Cover heruntergeladen: %s', 'Cover konnte nicht heruntergeladen werden'],ru: ['Скачать обложку', 'Обложка скачана: %s', 'Не удалось скачать обложку'],"zh-CN": ['下载封面', '封面已下载: %s', '下载封面失败'],ja: ['カバーをダウンロード', 'カバーがダウンロードされました: %s', 'カバーのダウンロードに失敗しました']};function initScript(saveAs) {let [buttonText, downloadedText, errorText] = translations.en;// Set language based on browser settingsconst lang = navigator.language.split('-')[0];if (lang in translations) {[buttonText, downloadedText, errorText] = translations[lang];}// Add stylesconst style = document.createElement('style');style.innerText = `.cover-download-btn {width: 32px;height: 32px;border-radius: 50%;border: 0;background-color: #1fdf64;background-image: url('data:image/svg+xml;utf8,<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120.59 122.88"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>download-image</title><path class="cls-1" d="M0,0H120.59V93.72H87.84V83.64H109.5V10.08H11.1V83.64H32.2V93.72H0V0ZM51.64,101.3V83.63H68.51V101.3H80.73L60.31,122.88,39.85,101.3ZM33.92,24.93a7.84,7.84,0,1,1-7.85,7.84,7.85,7.85,0,0,1,7.85-7.84Zm33,33.66L82.62,31.46,99.29,73.62H21.5V68.39L28,68.07l6.53-16,3.27,11.44h9.8l8.5-21.89,10.79,17Z"/></svg>');background-size: 20px 20px;background-position: center;background-repeat: no-repeat;cursor: pointer;margin-left: 10px;}.cover-download-btn:hover {transform: scale(1.1);}`;document.head.appendChild(style);function downloadImage(imageSrc, fileName) {return new Promise((resolve, reject) => {GM.xmlHttpRequest({method: 'GET',url: imageSrc,responseType: 'blob',onload: function(response) {try {const blob = response.response;saveAs(blob, fileName);showInfo(downloadedText.replace('%s', fileName), 'success');resolve();} catch (error) {console.error('Error saving file:', error);reject(error);}},onerror: function(error) {console.error('Error downloading image:', error);reject(error);}});});}const albumInfoCache = new Map();function getAlbumInfo() {const cacheKey = window.location.href;if (albumInfoCache.has(cacheKey)) {return albumInfoCache.get(cacheKey);}let artistName, albumName;// Desktop versionconst desktopArtistElements = document.querySelectorAll('div.Fb61sprjhh75aOITDnsJ a[data-testid="creator-link"]');if (desktopArtistElements.length > 0) {const artistNames = Array.from(desktopArtistElements).map(el => el.textContent.trim());artistName = artistNames.join(', ');albumName = document.querySelector('h1.encore-text.encore-text-headline-large.encore-internal-color-text-base[data-encore-id="text"]')?.textContent;}// Mobile versionelse {artistName = document.querySelector('.b81TNrTkVyPCOH0aDdLG a')?.textContent;albumName = document.querySelector('.gj6rSoF7K4FohS2DJDEm')?.textContent;}const info = {artist: artistName || 'Unknown Artist',album: albumName ? albumName.trim() : 'Unknown Album'};albumInfoCache.set(cacheKey, info);return info;}function findAlbumCover() {const albumPageSection = document.querySelector('section[data-testid="album-page"]');if (!albumPageSection) {return null;}let coverElement = albumPageSection.querySelector('img[srcset*="i.scdn.co/image/"][sizes]');if (!coverElement) {const albumLinks = albumPageSection.querySelectorAll('a[href^="/album/"]');for(let link of albumLinks) {coverElement = link.closest('section').querySelector('img[srcset*="i.scdn.co/image/"][sizes]');if (coverElement) {break;}}}if (!coverElement) {const modalButton = albumPageSection.querySelector('button[class*="osiFNXU9Cy1X0CYaU9Z"]');if (modalButton) {coverElement = modalButton.querySelector('img[src*="i.scdn.co/image/"]');}}if (coverElement && coverElement.width >= 200) {return coverElement;}return null;}function getFullSizeCoverUrl() {const coverElement = findAlbumCover();if (!coverElement) {console.log('Cover element not found');return null;}const coverUrl = coverElement.src;for (const size of CONFIG.SIZES) {if (coverUrl.includes(size)) {return coverUrl.replace(size, CONFIG.FULL_SIZE);}}console.log('Failed to extract cover hash');return null;}function debounce(func, delay) {let timeoutId;return function (...args) {clearTimeout(timeoutId);timeoutId = setTimeout(() => func.apply(this, args), delay);};}function shouldShowButton() {return window.location.pathname.startsWith('/album/');}function addDownloadButton() {if (!shouldShowButton()) return;let actionBarRow = document.querySelector('[data-testid="action-bar-row"]');// If desktop version's action bar is not found, try a mobile versionif (!actionBarRow) {actionBarRow = document.querySelector('.jXbmfyIkvfBoDgVxAaDD');}if (actionBarRow && !actionBarRow.querySelector('.cover-download-btn')) {const downloadButton = document.createElement('button');downloadButton.classList.add('cover-download-btn');downloadButton.title = buttonText;downloadButton.addEventListener('click', async function() {const fullSizeCoverUrl = getFullSizeCoverUrl();if (fullSizeCoverUrl) {try {const albumInfo = getAlbumInfo();const fileName = `${albumInfo.artist} - ${albumInfo.album}.jpg`;await downloadImage(fullSizeCoverUrl, fileName);} catch (error) {showInfo(errorText, 'error');}} else {showInfo(errorText, 'error');}});actionBarRow.appendChild(downloadButton);}}function removeDownloadButton() {const existingButton = document.querySelector('.cover-download-btn');if (existingButton) {existingButton.remove();}}function addOrRemoveButton() {if (shouldShowButton()) {addDownloadButton();} else {removeDownloadButton();}}function showInfo(str, type = 'success') {const infoDiv = document.createElement('div');infoDiv.style.cssText = `position: fixed;bottom: 20px;left: 50%;transform: translateX(-50%);background-color: ${type === 'success' ? '#1db954' : '#ff4444'};color: white;padding: 10px 20px;border-radius: 5px;z-index: 9999;`;infoDiv.textContent = str;document.body.appendChild(infoDiv);setTimeout(() => infoDiv.remove(), 3000);}const debouncedAddOrRemoveButton = debounce(addOrRemoveButton, CONFIG.DEBOUNCE_DELAY);function init() {// Initial checkaddOrRemoveButton();// Listen for URL changesif ('onurlchange' in window) {window.addEventListener('urlchange', () => {debouncedAddOrRemoveButton();});}// Observe DOM changesconst observer = new MutationObserver(() => {debouncedAddOrRemoveButton();});observer.observe(document.body, {childList: true,subtree: true});// Listen for History API changesconst originalPushState = history.pushState;history.pushState = function() {originalPushState.apply(this, arguments);debouncedAddOrRemoveButton();};window.addEventListener('popstate', debouncedAddOrRemoveButton);}init();}// Load FileSaver.js and initialize scriptif (typeof saveAs === 'undefined') {const script = document.createElement('script');script.src = 'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js';script.onload = () => initScript(saveAs);document.head.appendChild(script);} else {initScript(saveAs);}})();