Skip to content

Instantly share code, notes, and snippets.

@esavelyeva
Last active November 10, 2016 13:59
Show Gist options
  • Save esavelyeva/0e245d9e0979cc8f7fd0d4cc74dfcb3a to your computer and use it in GitHub Desktop.
Save esavelyeva/0e245d9e0979cc8f7fd0d4cc74dfcb3a to your computer and use it in GitHub Desktop.
Go to http://bookmarklets.org/maker/ to generate the bookmarklet
//download.js v3.0, by dandavis; 2008-2014. [CCBY2] see http://danml.com/download.html for tests/usage
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support
// data can be a string, Blob, File, or dataURL
function download(data, strFileName, strMimeType) {
var self = window, // this script is only for browsers anyway...
u = "application/octet-stream", // this default mime also triggers iframe downloads
m = strMimeType || u,
x = data,
D = document,
a = D.createElement("a"),
z = function (a) {
return String(a);
},
B = self.Blob || self.MozBlob || self.WebKitBlob || z,
BB = self.MSBlobBuilder || self.WebKitBlobBuilder || self.BlobBuilder,
fn = strFileName || "download",
blob,
b,
ua,
fr;
//if(typeof B.bind === 'function' ){ B=B.bind(self); }
if (String(this) === "true") { //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
x = [x, m];
m = x[0];
x = x[1];
}
//go ahead and download dataURLs right away
if (String(x).match(/^data\:[\w+\-]+\/[\w+\-]+[,;]/)) {
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
navigator.msSaveBlob(d2b(x), fn) :
saver(x); // everyone else can save dataURLs un-processed
} //end if dataURL passed?
try {
blob = x instanceof B ?
x :
new B([x], {
type: m
});
} catch (y) {
if (BB) {
b = new BB();
b.append([x]);
blob = b.getBlob(m); // the blob
}
}
function d2b(u) {
var p = u.split(/[:;,]/),
t = p[1],
dec = p[2] == "base64" ? atob : decodeURIComponent,
bin = dec(p.pop()),
mx = bin.length,
i = 0,
uia = new Uint8Array(mx);
for (i; i < mx; ++i) uia[i] = bin.charCodeAt(i);
return new B([uia], {
type: t
});
}
function saver(url, winMode) {
if ('download' in a) { //html5 A[download]
a.href = url;
a.setAttribute("download", fn);
a.innerHTML = "downloading...";
D.body.appendChild(a);
setTimeout(function () {
a.click();
D.body.removeChild(a);
if (winMode === true) {
setTimeout(function () {
self.URL.revokeObjectURL(a.href);
}, 250);
}
}, 66);
return true;
}
//do iframe dataURL download (old ch+FF):
var f = D.createElement("iframe");
D.body.appendChild(f);
if (!winMode) { // force a mime that will download:
url = "data:" + url.replace(/^data:([\w\/\-\+]+)/, u);
}
f.src = url;
setTimeout(function () {
D.body.removeChild(f);
}, 333);
} //end saver
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
return navigator.msSaveBlob(blob, fn);
}
if (self.URL) { // simple fast and modern way using Blob and URL:
saver(self.URL.createObjectURL(blob), true);
} else {
// handle non-Blob()+non-URL browsers:
if (typeof blob === "string" || blob.constructor === z) {
try {
return saver("data:" + m + ";base64," + self.btoa(blob));
} catch (y) {
return saver("data:" + m + "," + encodeURIComponent(blob));
}
}
// Blob but not URL:
fr = new FileReader();
fr.onload = function (e) {
saver(this.result);
};
fr.readAsDataURL(blob);
}
return true;
} /* end download() */
/**
* Traverses the DOM nodes upwards to find a parent element with the specified class
* @param {DOM Node} child Child node
* @param {String} parentClass Parent class
* @return {Dom Node / null} Parent node with the specified class or null
*/
function getParentFolder(child, parentClass) {
var parent = child.parentNode;
if (parent.className === parentClass) {
return parent;
} else if (parent.tagName.toLowerCase() === 'body') {
return null;
} else {
return getParentFolder(parent, parentClass);
}
}
/**
* Gets video URL addresses and titles
* @return {Object / null} Collection of videos or null
*/
function getVideoLinks() {
var folderClass = 'folder-current',
folderEl = document.querySelector('.' + folderClass);
if (!folderEl) {
return null;
}
var seasonFolderClass = 'folder',
videoTitleClass = 'b-tab-item__title-inner',
videoTitle = document.querySelector('.' + videoTitleClass).querySelector('span').innerHTML.replace(/[^a-zAа-яА-Я\d]/g, ''),
videoSeasonEl = getParentFolder(folderEl, seasonFolderClass),
videoSeason = videoSeasonEl ? videoSeasonEl.querySelector('.header a b').innerHTML.replace(/ /g, '') : null,
videoClass = 'b-file-new',
videoEls = folderEl.querySelectorAll('.' + videoClass),
linkClass = 'b-file-new__link-material-download',
data = {
title: videoTitle + '_' + (videoSeason ? videoSeason : ''),
videos: []
};
for (var i = 0; i < videoEls.length; i++) {
var video = {};
//Get only the videos currently displayed on page
if (videoEls[i].style.display !== 'none') {
//Get URL
video.href = videoEls[i].querySelectorAll('.' + linkClass)[0].href;
//Get meta
var splitHref = video.href.split('/');
video.meta = decodeURIComponent(splitHref[splitHref.length - 1]).replace(/[+]/g, ' ').toLowerCase();
//Add video to the collection
data.videos.push(video);
}
}
return data.videos.length > 0 ? data : null;
}
/**
* Creates plain text file containing M3U playlist
* @param {Array} videos Collection of video URL addresses and meta data
* @return {String} Playlist
*/
function createPlaylist(videos) {
var playlist = '#EXTM3U \n';
for (var i = 0; i < videos.length; i++) {
var video = '#EXTINF:' + (i + 1) + ', ' + videos[i].meta + '\n' + videos[i].href + '\n';
playlist = playlist.concat(video);
}
return playlist;
}
/**
* Create markup for the popup containing the videos
* @param {Array} videos Collection of video URL addresses and meta data
* @return {document fragment} Popup markup
*/
function createPopupMarkup(data) {
var bodyRect = document.querySelector('body').getBoundingClientRect(),
popupId = 'bml-popup-' + Date.now(),
downloadBtnId = 'bml-download-playlist',
dismissBtnId = 'bml-dismiss';
/**
* Prompts downloading of plain text file
*/
function onClick(ev) {
if (ev.target.id === downloadBtnId) {
var fileName = data.title + '.m3u';
download(createPlaylist(data.videos), fileName, 'text/plain');
} else if (ev.target.id === dismissBtnId) {
var html = document.querySelector('html'),
popup = document.querySelector('#' + popupId);
popup.parentNode.removeChild(popup);
html.style.overflow = 'auto';
}
}
var frag = document.createDocumentFragment(),
popup = document.createElement('div'),
heading = document.createElement('p'),
list = document.createElement('ul'),
downloadBtn = document.createElement('button'),
dismissBtn = document.createElement('button'),
popupPadding = 24;
//Popup styles
popup.style.background = '#fff';
popup.style.position = 'absolute';
popup.style.top = (-bodyRect.top) + 'px';
popup.style.bottom = (-bodyRect.top) + window.innerHeight + 'px';
popup.style.right = 0;
popup.style.left = '50%';
popup.style.minWidth = '300px';
popup.style.minHeight = window.innerHeight - popupPadding * 2 + 'px';
popup.style.padding = popupPadding + 'px';
popup.style.overflow = 'scroll';
popup.style.fontFamily = 'Helvetica, Arial, sans-serif';
popup.style.fontSize = '16px';
popup.style.lineHeight = '1.618';
popup.style.transform = 'translate(-50%, 0)';
popup.style.zIndex = 9999;
popup.style.boxShadow = '0px 0px 10px 5px hsla(0, 0%, 0%, .3333)';
heading.textContent = data.title;
heading.style.fontSize = '20px';
heading.style.marginBottom = '12px';
popup.appendChild(heading);
for (var i = 0; i < data.videos.length; i++) {
var item = document.createElement('li');
link = document.createElement('a');
link.href = data.videos[i].href;
link.textContent = data.videos[i].meta;
item.appendChild(link);
list.appendChild(item);
}
list.style.marginBottom = '20px';
popup.appendChild(list);
//Download button base
downloadBtn.type = 'button';
downloadBtn.textContent = 'Download playlist';
downloadBtn.id = downloadBtnId;
//Download button styles
downloadBtn.style.width = '100%';
downloadBtn.style.padding = '12px 16px';
downloadBtn.style.fontSize = '16px';
downloadBtn.style.color = '#fff';
downloadBtn.style.background = '#0074D9';
downloadBtn.style.marginBottom = '12px';
downloadBtn.style.cursor = 'pointer';
//Add button to popup
popup.appendChild(downloadBtn);
//Dismiss button base
dismissBtn.type = 'button';
dismissBtn.textContent = 'Dismiss';
dismissBtn.id = dismissBtnId;
//Dismiss button styles
dismissBtn.style.width = '100%';
dismissBtn.style.padding = '12px 16px';
dismissBtn.style.fontSize = '16px';
dismissBtn.style.color = '#fff';
dismissBtn.style.background = '#AAAAAA';
dismissBtn.style.marginBottom = '12px';
dismissBtn.style.cursor = 'pointer';
//Add button to popup
popup.appendChild(dismissBtn);
//Add event listener for download and dismiss buttons
document.addEventListener('click', onClick);
popup.id = popupId;
frag.appendChild(popup);
return frag;
}
/**
* Attach the document fragment to document body
* @param {document fragment} frag [description]
*/
function attachFrag(frag) {
var html = document.querySelector('html'),
body = document.querySelector('body');
html.style.overflow = 'hidden';
body.appendChild(frag);
}
/**
* Init extraction of the playlist from fs.to
*/
function init() {
var data = getVideoLinks();
if (data) {
createPlaylist(data);
attachFrag(createPopupMarkup(data));
} else {
alert('Pick videos folder first.');
}
}
init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment