您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fix the download button to actually download the audio instead of opening the login screen
// ==UserScript== // @name forvo.com - Make download button actually download audios // @namespace secretx_scripts // @match *://forvo.com/word/* // @match *://*.forvo.com/word/* // @version 2023.12.16 // @author SecretX // @description Fix the download button to actually download the audio instead of opening the login screen // @grant GM.xmlHttpRequest // @run-at document-start // @icon https://i.imgur.com/3hA6TF1.png // @license GNU LGPLv3 // ==/UserScript== const forvoServerUrl = "https://audio12.forvo.com"; function doRequest(httpMethod, url) { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: httpMethod.toUpperCase(), url: url, onload: resolve, onerror: reject, responseType: "blob", timeout: 6000, }); }); } function extractUrl(element) { const play = element.getAttribute("onclick"); // We are interested in Forvo's javascript Play function which takes in some parameters to play the audio // Example: Play(3060224,'OTQyN...','OTQyN..',false,'Yy9wL2NwXzk0MjYzOTZfNzZfMzM1NDkxNS5tcDM=','Yy9wL...','h') // Match anything that isn't commas, parentheses or quotes to capture the function arguments // Regex will match something like ["Play", "3060224", ...] const playArgs = play.match(/([^',\(\)]+)/g); // Forvo has two locations for mp3, /audios/mp3 and just /mp3 // /audios/mp3 is normalized and has the filename in the 5th argument of Play base64 encoded // /mp3 is raw and has the filename in the 2nd argument of Play encoded try { const file = atob(playArgs[5]); // Something like this: v/p/vp_9478059_76_3731369.mp3 return `${forvoServerUrl}/audios/mp3/${file}`; } catch (e) { // Some pronunciations don't have a normalized version so fallback to raw const file = atob(playArgs[2]); // Something like this: 9478059/76/9478059_76_3731369.mp3 return `${forvoServerUrl}/mp3/${file}`; } } function getPlayButtonFromDownloadButton(downloadButton) { let currentElement = downloadButton; for (let i = 0; i < 5; i++) { currentElement = currentElement.parentElement; } console.info("Current element", currentElement); return currentElement.querySelector(".play"); } function makeCopyOfDownloadButton(downloadButton) { const innerSpan = downloadButton.querySelector("* > span"); const text = innerSpan.innerText; // Create the new download button, following the structure of the old one const newDownloadButton = document.createElement(downloadButton.tagName.toLowerCase()); newDownloadButton.className = downloadButton.className; const newInnerSpan = document.createElement(innerSpan.tagName.toLowerCase()); newInnerSpan.className = innerSpan.className; newInnerSpan.innerText = text; newDownloadButton.appendChild(newInnerSpan); return newDownloadButton; } window.addEventListener("DOMContentLoaded", function () { const downloadButtons = Array.from(document.querySelectorAll(".download") ?? []); if (downloadButtons.length === 0) { console.debug("No download buttons found on this page, so not fixing anything..."); return; } for (const downloadButton of downloadButtons) { const newDownloadButton = makeCopyOfDownloadButton(downloadButton); const playButton = getPlayButtonFromDownloadButton(downloadButton); console.info("Play button", playButton); const url = extractUrl(playButton); const fileName = url.substring(url.lastIndexOf("/") + 1); // Add the download functionality to the new download button newDownloadButton.addEventListener("click", () => { console.info(`Downloading mp3 from: ${url}`); doRequest("GET", url).then((response) => { if (response.status !== 200) { console.error("Error on downloading mp3", response); return } const blob = response.response; const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.style.display = "none"; a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); console.info(`Downloaded '${fileName}'!`); }).catch((e) => { console.error("Error while downloading mp3!", e); }); }); // Replace the old download button with the new one downloadButton.replaceWith(newDownloadButton); } console.info(`Fixed ${downloadButtons.length} download buttons!`); }, false);