Facebook HD Video Downloader

Adds a download link for Facebook videos. Works for HD videos. Fork from styfle version. NEW!! Work on all page.

As of 30/06/2016. See the latest version.

  1. // ==UserScript==
  2. // @name Facebook HD Video Downloader
  3. // @description Adds a download link for Facebook videos. Works for HD videos. Fork from styfle version. NEW!! Work on all page.
  4. // @author EThaiZone
  5. // @include http://facebook.com/video.php*
  6. // @include http://*.facebook.com/video.php*
  7. // @include https://facebook.com/video.php*
  8. // @include https://*.facebook.com/video.php*
  9. // @include http://facebook.com/video/*
  10. // @include http://*.facebook.com/video/*
  11. // @include https://facebook.com/video/*
  12. // @include https://*.facebook.com/video/*
  13. // @include http://facebook.com/*/videos/*
  14. // @include http://*.facebook.com/*/videos/*
  15. // @include https://facebook.com/*/videos/*
  16. // @include https://*.facebook.com/*/videos/*
  17. // @include https://*.facebook.com/*
  18. // @version 0.1.6.3
  19. // @namespace https://greasyfork.org/users/3747
  20. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
  21. // ==/UserScript==
  22.  
  23.  
  24. //download.js v4.2, by dandavis; 2008-2016. [CCBY2] see http://danml.com/download.html for tests/usage
  25. // v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
  26. // v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
  27. // v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
  28. // v4 adds AMD/UMD, commonJS, and plain browser support
  29. // v4.1 adds url download capability via solo URL argument (same domain/CORS only)
  30. // v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
  31. // https://github.com/rndme/download
  32.  
  33. (function (root, factory) {
  34. if (typeof define === 'function' && define.amd) {
  35. // AMD. Register as an anonymous module.
  36. define([], factory);
  37. } else if (typeof exports === 'object') {
  38. // Node. Does not work with strict CommonJS, but
  39. // only CommonJS-like environments that support module.exports,
  40. // like Node.
  41. module.exports = factory();
  42. } else {
  43. // Browser globals (root is window)
  44. root.download = factory();
  45. }
  46. }(this, function () {
  47.  
  48. return function download(data, strFileName, strMimeType) {
  49.  
  50. var self = window, // this script is only for browsers anyway...
  51. defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
  52. mimeType = strMimeType || defaultMime,
  53. payload = data,
  54. url = !strFileName && !strMimeType && payload,
  55. anchor = document.createElement("a"),
  56. toString = function(a){return String(a);},
  57. myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
  58. fileName = strFileName || "download",
  59. blob,
  60. reader;
  61. myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
  62. if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
  63. payload=[payload, mimeType];
  64. mimeType=payload[0];
  65. payload=payload[1];
  66. }
  67.  
  68.  
  69. if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
  70. fileName = url.split("/").pop().split("?")[0];
  71. anchor.href = url; // assign href prop to temp anchor
  72. if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
  73. var ajax=new XMLHttpRequest();
  74. ajax.open( "GET", url, true);
  75. ajax.responseType = 'blob';
  76. ajax.onload= function(e){
  77. download(e.target.response, fileName, defaultMime);
  78. };
  79. setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
  80. return ajax;
  81. } // end if valid url?
  82. } // end if url?
  83.  
  84.  
  85. //go ahead and download dataURLs right away
  86. if(/^data\:[\w+\-]+\/[\w+\-]+[,;]/.test(payload)){
  87. if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
  88. payload=dataUrlToBlob(payload);
  89. mimeType=payload.type || defaultMime;
  90. }else{
  91. return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
  92. navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
  93. saver(payload) ; // everyone else can save dataURLs un-processed
  94. }
  95. }//end if dataURL passed?
  96.  
  97. blob = payload instanceof myBlob ?
  98. payload :
  99. new myBlob([payload], {type: mimeType}) ;
  100.  
  101.  
  102. function dataUrlToBlob(strUrl) {
  103. var parts= strUrl.split(/[:;,]/),
  104. type= parts[1],
  105. decoder= parts[2] == "base64" ? atob : decodeURIComponent,
  106. binData= decoder( parts.pop() ),
  107. mx= binData.length,
  108. i= 0,
  109. uiArr= new Uint8Array(mx);
  110.  
  111. for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
  112.  
  113. return new myBlob([uiArr], {type: type});
  114. }
  115.  
  116. function saver(url, winMode){
  117.  
  118. if ('download' in anchor) { //html5 A[download]
  119. anchor.href = url;
  120. anchor.setAttribute("download", fileName);
  121. anchor.className = "download-js-link";
  122. anchor.innerHTML = "downloading...";
  123. anchor.style.display = "none";
  124. document.body.appendChild(anchor);
  125. setTimeout(function() {
  126. anchor.click();
  127. document.body.removeChild(anchor);
  128. if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
  129. }, 66);
  130. return true;
  131. }
  132.  
  133. // handle non-a[download] safari as best we can:
  134. if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
  135. url=url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
  136. if(!window.open(url)){ // popup blocked, offer direct download:
  137. if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
  138. }
  139. return true;
  140. }
  141.  
  142. //do iframe dataURL download (old ch+FF):
  143. var f = document.createElement("iframe");
  144. document.body.appendChild(f);
  145.  
  146. if(!winMode){ // force a mime that will download:
  147. url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
  148. }
  149. f.src=url;
  150. setTimeout(function(){ document.body.removeChild(f); }, 333);
  151.  
  152. }//end saver
  153.  
  154.  
  155.  
  156.  
  157. if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
  158. return navigator.msSaveBlob(blob, fileName);
  159. }
  160.  
  161. if(self.URL){ // simple fast and modern way using Blob and URL:
  162. saver(self.URL.createObjectURL(blob), true);
  163. }else{
  164. // handle non-Blob()+non-URL browsers:
  165. if(typeof blob === "string" || blob.constructor===toString ){
  166. try{
  167. return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
  168. }catch(y){
  169. return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
  170. }
  171. }
  172.  
  173. // Blob but not URL support:
  174. reader=new FileReader();
  175. reader.onload=function(e){
  176. saver(this.result);
  177. };
  178. reader.readAsDataURL(blob);
  179. }
  180. return true;
  181. }; /* end download() */
  182. }));
  183.  
  184. // My code
  185. (function () {
  186.  
  187. function insertAfter(newNode, referenceNode) {
  188. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  189. }
  190. if (typeof jQuery == 'undefined') {
  191. alert('[Facebook HD Video Downloader]\n\nYour greasemonkey or tampermonkey is too old. Please update.');
  192. return;
  193. }
  194.  
  195. var patternfbh = /facebook\.com\/(.*?)/;
  196. if (! document.URL.match(patternfbh)) {
  197. return;
  198. }
  199.  
  200. jQuery('head').append('<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">');
  201.  
  202. var css = "\
  203. .dl {\
  204. display: inline-block;\
  205. width: 1em;\
  206. height: 1em;\
  207. }\
  208. .dl_container {\
  209. z-index: 9999;\
  210. position: relative;\
  211. float: right;\
  212. margin: 5px 5px 0 0;\
  213. padding: 2px 4px 2px 4px;\
  214. background: white;\
  215. border-radius: 4px;\
  216. opacity: 0.5;\
  217. transition: opacity .25s ease-in-out;\
  218. -moz-transition: opacity .25s ease-in-out;\
  219. -webkit-transition: opacity .25s ease-in-out;\
  220. color: black !important;\
  221. text-decoration: none;\
  222. }\
  223. .dl_container:hover {\
  224. opacity: 1;\
  225. text-decoration: none;\
  226. }\
  227. ";
  228.  
  229. jQuery('head').append('<style type="text/css">' + css + '</style>');
  230.  
  231. var iconDownload = jQuery('<div/>');
  232. iconDownload = jQuery('<i class="fa fa-download dl"></i>');
  233. function renderFBDownloader(counter) {
  234.  
  235. // Get the base so we can append to it later
  236. var base = jQuery('.comment_link').parent('span').parent('div');
  237. if (base.length === 0) {
  238. base = jQuery('.UFILikeLink').parent('div').parent('span').parent('div'); // check link like
  239. }
  240. if (base.length === 0) {
  241. base = jQuery('.userContent'); // or else I will attach to userContent.
  242. }
  243. base = base[0];
  244.  
  245. /*
  246. * NOTE: Don't use jquery on <video>, it will broke them.
  247. */
  248.  
  249. // Get all the <embed> elements
  250. var videoElements = document.querySelectorAll('video:not([fb_download_link_ethaizone])');
  251. var embedElements = document.querySelectorAll('embed[flashvars]');
  252.  
  253. for (var i = 0; i < videoElements.length; i++) {
  254.  
  255. var embed = videoElements[i].querySelectorAll('embed[flashvars]');
  256.  
  257. if (embed.length === 0) {
  258. continue;
  259. }
  260.  
  261. videoElements[i].setAttribute('fb_download_link_ethaizone', '1');
  262.  
  263. embed = embed[0];
  264.  
  265. // Get the flashvars attribute and decode it
  266. var flashvars = decodeURIComponent(embed.getAttribute('flashvars'));
  267.  
  268. // Check if this string contains the code we're looking for
  269. var hd_src_index = flashvars.indexOf('hd_src');
  270. var p_width_index = flashvars.indexOf('&width=');
  271. if (hd_src_index > -1 && p_width_index > -1) {
  272. // This string contains the payload we are looking for so parse it
  273. var obj = JSON.parse(flashvars.slice(7, p_width_index));
  274.  
  275. if (typeof obj.video_data.progressive == 'undefined') {
  276. log('Something wrong in obj.');
  277. console.log(obj);
  278. continue;
  279. }
  280. var video_data = obj.video_data.progressive;
  281.  
  282. //console.log(video_data);
  283. if (typeof video_data.video_id == 'undefined') {
  284. console.log(video_data);
  285. } else {
  286. var title = video_data.video_id;
  287. }
  288. //var title = document.querySelectorAll('h2.uiHeaderTitle')[0].innerText;
  289.  
  290. // thank css style from fork of nhtera.
  291. var link = jQuery('<a/>');
  292. link.addClass('dl_container');
  293. link.addClass('fbPhotosPhotoActionsItem');
  294. link.addClass('fb_download_link_ethaizone');
  295. link.attr('fb_download_link_ethaizone');
  296. link.append(iconDownload[0].outerHTML);
  297. link.append('&nbsp;');
  298.  
  299. // High Def
  300. if (typeof video_data.hd_src != 'undefined' && video_data.hd_src)
  301. {
  302. link.append('<span class="status">Download (HD)</span>');
  303. link.on('click', function(){
  304. if (link.data('dl') == true) {
  305. alert('It\'s downloading. Please wait.');
  306. return;
  307. }
  308. link.find('.status').text('Downloading...');
  309. alert('Download in backgrond. Please wait a minute...');
  310. link.data('dl', true);
  311. var x=new XMLHttpRequest();
  312. x.open("GET", video_data.hd_src, true);
  313. x.responseType = 'blob';
  314. x.onload=function(e){
  315. link.data('dl', false);
  316. link.find('.status').text('Download (HD)');
  317. download(x.response, title + '_hd.mp4', x.response.type );
  318. };
  319. x.send();
  320. });
  321. insertAfter(link[0], videoElements[i]);
  322. } else if (typeof video_data.sd_src != 'undefined' && video_data.sd_src)
  323. {
  324. link.append('<span class="status">Download (SD)</span>');
  325. link.on('click', function(){
  326. if (link.data('dl') == true) {
  327. alert('It\'s downloading. Please wait.');
  328. return;
  329. }
  330. link.find('.status').text('Downloading...');
  331. alert('Download in backgrond. Please wait a minute...');
  332. link.data('dl', true);
  333. var x=new XMLHttpRequest();
  334. x.open("GET", video_data.sd_src, true);
  335. x.responseType = 'blob';
  336. x.onload=function(e){
  337. link.data('dl', false);
  338. link.find('.status').text('Download (SD)');
  339. download(x.response, title + '_sd.mp4', x.response.type );
  340. };
  341. x.send();
  342. });
  343. insertAfter(link[0], videoElements[i]);
  344. } else {
  345. log('Something wrong in video_data.');
  346. console.log(video_data);
  347. continue;
  348. }
  349.  
  350. log('Success.');
  351. } // end if
  352.  
  353. } // end loop
  354.  
  355. return videoElements.length;
  356. }
  357.  
  358. var counter = 0;
  359. var delay = 1500;
  360. function doExec() {
  361. if (jQuery('#mainContainer').length < 1) {
  362. // log('No maincontaimer.');
  363. return false;
  364. }
  365. counter++;
  366. try {
  367. var found = renderFBDownloader(counter);
  368. // if (found !== 0 && delay == 1000) {
  369. // delay = 3000;
  370. // log('First found. Decrease delay. ('+ delay +')');
  371. // }
  372.  
  373. // if (counter > 10 && delay == 1000) {
  374. // delay = 5000;
  375. // log('Too long and not found anything. Decrease delay. ('+ delay +')');
  376. // }
  377. // if (counter < 30) {
  378. // setTimeout(doExec, delay);
  379. // }
  380. setTimeout(doExec, delay);
  381. // log('Check!! No:'+counter+' Found: ' + renderFBDownloader(counter));
  382. } catch(e) {
  383. log("Found error!");
  384. console.log(e);
  385. //setTimeout(doExec, 1000);
  386. }
  387.  
  388. return true;
  389. }
  390.  
  391. function log(msg) {
  392. //alert(msg);
  393. console.log("[FB Video Downloader] " + msg);
  394. }
  395. if (doExec()) {
  396. var myVersion = GM_info.script.version;
  397. log("First start. Version "+myVersion);
  398. }
  399. })();