Download audio files directly from Forvo website without account.
// ==UserScript==
// @name ForvoDDL
// @namespace https://yveone.com/ForvoDDL
// @version 1.0.4
// @description Download audio files directly from Forvo website without account.
// @author YveOne (Yvonne P.)
// @license MIT; https://opensource.org/licenses/MIT
// @include https://*.forvo.com/*
// @include https://audio00.forvo.com/*
// @grant none
// ==/UserScript==
/*global
_SERVER_HOST
_AUDIO_HTTP_HOST
defaultProtocol
*/
(function() {
'use strict';
if (location.host === "audio00.forvo.com") {
if (location.hash) {
let [file, name] = JSON.parse(decodeURIComponent(location.hash.substr(1)));
let a = document.createElement('a');
a.setAttribute('href', file);
a.setAttribute('download', name);
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
return;
}
const rePlayData = /^play\((\d+),'([[A-Za-z0-9+\/=]+)?','([[A-Za-z0-9+\/=]+)?',(true|false),'([[A-Za-z0-9+\/=]+)?','([[A-Za-z0-9+\/=]+)?','([\w]+)?'\);.*?$/i;
const selectorPlayButton = "span.play[onclick]";
const reSearchTranslationLocation = /https\:\/\/(?:\w+\.)forvo\.com\/search-translation\/(.*?)\/(.*?)\//i;
const reSearchLocation = /https\:\/\/(?:\w+\.)forvo\.com\/search\/(.*?)\//i;
const reWordLocation = /https\:\/\/(?:\w+\.)forvo\.com\/word\/(.*?)\//i;
const reUserLocation = /https\:\/\/(?:\w+\.)forvo\.com\/user\/(.*?)\/.*?\//i;
const rePhraseLocation = /https\:\/\/(?:\w+\.)forvo\.com\/phrase\/(.*?)\//i;
const reLangPronLocation = /https\:\/\/(?:\w+\.)forvo\.com\/languages-pronunciations\/.*?\//i;
const reTagListLocation = /https\:\/\/(?:\w+\.)forvo\.com\/tag\/.*?\//i;
function getPlayData(playButton) {
let m = playButton.getAttribute("onclick").match(rePlayData);
if (!m) {
return false;
}
//m = [_, id, mp3, ogg, b, _mp3, _ogg, u]
let id = parseInt(m[1]);
let mp3 = defaultProtocol + "//" + _AUDIO_HTTP_HOST + "/mp3/" + atob(m[2]);
let ogg = defaultProtocol + "//" + _AUDIO_HTTP_HOST + "/ogg/" + atob(m[3]);
return {id, mp3, ogg};
}
function downloadAudio(href, filename) {
let i = document.createElement('iframe');
i.setAttribute('src', defaultProtocol + "//" + _AUDIO_HTTP_HOST + "#" + JSON.stringify([href, filename]));
i.style.display = 'none';
document.body.appendChild(i);
setTimeout(function() {
document.body.removeChild(i);
}, 1000);
}
function decUrl(str) {
return decodeURIComponent(str).replace(/_/g, " ");
}
function onclick(e) {
let row = e.target.parentNode.parentNode;
let playButton = row.querySelector(selectorPlayButton);
let fileType = e.target.getAttribute("data-type");
let audioData = getPlayData(playButton);
let filename = `audio.${fileType}`;
if (reSearchTranslationLocation.test(location.href)) {
let m = location.href.match(reSearchTranslationLocation);
let search = decUrl(m[1]);
let lang = m[2];
let word = row.querySelector("a.word").innerHTML;
filename = `${search} - ${word}.${fileType}`;
} else if (reSearchLocation.test(location.href)) {
let search = row.querySelector("a.word").innerHTML;
filename = `${search}.${fileType}`;
} else if (reWordLocation.test(location.href)) {
let m = location.href.match(reWordLocation);
let word = decUrl(m[1]);
let user = row.querySelector("span.ofLink").innerHTML;
filename = `${word} (by ${user}).${fileType}`;
} else if (reUserLocation.test(location.href)) {
let m = location.href.match(reUserLocation);
let user = decUrl(m[1]);
row = row.parentNode;
let word = row.querySelector("a.word").innerHTML;
filename = `${word} (by ${user}).${fileType}`;
} else if (rePhraseLocation.test(location.href)) {
let m = location.href.match(rePhraseLocation);
let phrase = decUrl(m[1]);
let user = row.querySelector("span.ofLink").innerHTML;
filename = `${phrase} (by ${user}).${fileType}`;
} else if (reLangPronLocation.test(location.href)) {
let m = location.href.match(reLangPronLocation);
let word = row.querySelector("a.word").innerHTML;
filename = `${word}.${fileType}`;
} else if (reTagListLocation.test(location.href)) {
let m = location.href.match(reTagListLocation);
let word = row.querySelector("a.word").innerHTML;
filename = `${word}.${fileType}`;
}
downloadAudio(audioData[fileType], filename);
}
function readDoc(doc) {
Array.from(doc.querySelectorAll(selectorPlayButton)).forEach((playBtn, i) => {
playBtn.setAttribute("style", "position: relative;");
playBtn.parentNode.setAttribute("style", "padding-left: 0;");
let mp3 = document.createElement("a");
mp3.appendChild(document.createTextNode("mp3"));
mp3.setAttribute("data-type", "mp3");
mp3.setAttribute("style", "cursor: pointer;");
mp3.addEventListener("click", onclick);
let ogg = document.createElement("a");
ogg.appendChild(document.createTextNode("ogg"));
ogg.setAttribute("data-type", "ogg");
ogg.setAttribute("style", "cursor: pointer;");
ogg.addEventListener("click", onclick);
let span = document.createElement("span");
span.appendChild(document.createTextNode("["));
span.appendChild(mp3);
span.appendChild(document.createTextNode("]"));
span.appendChild(document.createTextNode("["));
span.appendChild(ogg);
span.appendChild(document.createTextNode("]"));
playBtn.parentNode.insertBefore(span, playBtn);
});
}
readDoc(document);
})();