Skip to content

Instantly share code, notes, and snippets.

@Mordo95
Last active February 20, 2024 23:39
Show Gist options
  • Save Mordo95/141ead6ffa1fb6557db127d744a39e6f to your computer and use it in GitHub Desktop.
Save Mordo95/141ead6ffa1fb6557db127d744a39e6f to your computer and use it in GitHub Desktop.
Tampermonkey script to add a download button to facebook, reddit and youtube videos
// ==UserScript==
// @name Video Downloader for Tampermonkey
// @version 0.4
// @description Will add a download button to Reddit, Facebook and Youtube videos
// @author Github/Mordo95
// @match *://*/*
// @grant GM_xmlhttpRequest
// @run-at document-start
// ==/UserScript==
// version 0.4 fixes reddit thumbnails also receiving a button
(function() {
'use strict';
class Mordo95DL {
static addStyle(css) {
const style = document.getElementById("GM_addStyleBy8626") || (function() {
const style = document.createElement('style');
style.type = 'text/css';
style.id = "GM_addStyleBy8626";
document.head.appendChild(style);
return style;
})();
const sheet = style.sheet;
sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
}
static paramsToObject(entries) {
const result = {}
for(const [key, value] of entries) { // each 'entry' is a [key, value] tupple
result[key] = value;
}
return result;
}
static buildParams(p) {
return new URLSearchParams(p).toString();
}
}
class FBDL {
getReactFiber(el) {
for (let prop of Object.keys(el)) {
if (prop.startsWith("__reactFiber")) {
return el[prop];
}
}
return null;
}
fiberReturnUntil(fiber, displayName) {
let fiberInst = fiber;
while (fiberInst != null) {
let fiberInstName = "";
if (typeof fiberInst.elementType === "string")
fiberInstName = fiberInst.elementType;
else if (typeof fiberInst.elementType === "function")
fiberInstName = fiberInst.elementType.displayName;
if (fiberInstName === displayName)
return fiberInst;
fiberInst = fiberInst.return;
}
return null;
}
parentsUntil(el, c) {
let elInst = el;
while (elInst != null) {
if (elInst.classList.toString() === c)
return elInst;
elInst = elInst.parentElement;
}
return null;
}
getVideoImplementation(fiber, impl = "VideoPlayerProgressiveImplementation") {
if(!fiber || !fiber.memoizedProps || !fiber.memoizedProps.implementations)
return null;
return fiber.memoizedProps.implementations.find(x => x.typename === impl);
}
addVideoButton(on, videoEl) {
let btn = document.createElement("div");
btn.innerHTML = "Download (HD)";
btn.classList.add("dlBtn");
btn.onclick = () => this.btnAct(videoEl);
//let parent = parentsUntil(videoEl, videoEl.classList[0]) || videoEl.parentElement;
on.prepend(btn);
}
btnAct(videoEl) {
let fiber = this.getReactFiber(videoEl);
let props = this.fiberReturnUntil(fiber, "a [from CoreVideoPlayer.react]");
let impl = this.getVideoImplementation(props);
if (impl.data.hdSrc) {
window.open(impl.data.hdSrc);
} else {
window.open(impl.data.sdSrc);
}
}
inject() {
setInterval(() => {
let videos = document.querySelectorAll("video:not([data-tagged])");
for (let video of videos) {
video.setAttribute("data-tagged", "true");
let fiber = this.getReactFiber(video.parentElement);
let props = this.fiberReturnUntil(fiber, "a [from CoreVideoPlayer.react]");
this.addVideoButton(document.querySelector(`[data-instancekey='${props.memoizedState.memoizedState}']`), video.parentElement);
}
}, 200);
Mordo95DL.addStyle(".dlBtn{position:absolute;top:0;right:0;z-index:99999;padding:10px 15px;margin:5px;cursor:pointer;outline:0;background:var(--primary-button-background);color:var(--primary-button-text);border:1px solid 1px solid var(--accent);font-family: var(--font-family-segoe)!important}");
Mordo95DL.addStyle(".dlBtn:hover{background-color:var(--primary-button-pressed)}");
}
}
class YTDL {
addVideoButton(on) {
let btn = document.createElement("div");
btn.innerHTML = this.btnText;
btn.classList.add("dlBtn");
btn.onclick = () => this.getLinks(btn);
on.prepend(btn);
}
getLinks(btn) {
let fd = new FormData();
fd.set("q", window.location.href);
fd.set("vt", "mp4");
let url = "https://yt1s.com/api/ajaxSearch/index";
GM_xmlhttpRequest({
method: 'POST',
url,
data: fd,
onload: (resp) => {
let js = JSON.parse(resp.responseText);
this.convert(btn, js.vid, js.links.mp4.auto.k);
}
});
}
convert(btn, vid, k) {
let fd = new FormData();
fd.set("vid", vid);
fd.set("k", k);
btn.innerHTML = "Converting ...";
GM_xmlhttpRequest({
method: 'POST',
url: 'https://yt1s.com/api/ajaxConvert/convert',
data: fd,
timeout: 60000,
onload: (resp) => {
let js = JSON.parse(resp.responseText);
let status = js.c_status;
if (status === "CONVERTED") {
window.open(js.dlink);
} else {
alert("Error converting video. Please try again later!");
}
btn.innerHTML = this.btnText;
},
onTimeout: () => { btn.innerHTML = this.btnText; }
});
}
inject() {
setInterval(() => {
let videos = document.querySelectorAll("video:not([data-tagged])");
for (let video of videos) {
video.setAttribute("data-tagged", "true");
console.log(document.querySelector("#container"));
this.addVideoButton(document.querySelector("#container.ytd-player"));
}
}, 200);
Mordo95DL.addStyle(".dlBtn{position:absolute;top:0;right:0;z-index:99999;padding:10px 15px;margin:5px;cursor:pointer;outline:0;background:#5383FB;color:white;border:1px solid 1px solid #5383FB;font-family: Segoe UI Historic, Segoe UI, Helvetica, Arial, sans-serif !important;font-size:12px;}");
Mordo95DL.addStyle(".dlBtn:hover{background-color:#86A4FC}");
}
constructor() {
this.btnText = "Download (HD)";
}
}
class RDDL {
addVideoButton(on, videoEl) {
on.querySelectorAll(".dlBtn").forEach(el => el.remove());
let btn = document.createElement("div");
btn.innerHTML = this.btnText;
btn.classList.add("dlBtn");
btn.onclick = () => this.btnAct(btn);
on.prepend(btn);
}
btnAct(btn) {
let src = this.returnUntil(this.getReactInternalState(btn.parentElement), "mpegDashSource");
if (!src) {
alert("Unable to load video data");
return;
}
let mpegDashUrl = src.pendingProps.mpegDashSource;
let match = mpegDashUrl.match(/https:\/\/v.redd.it\/(?<videoId>.+)\/DASHPlaylist\.mpd/);
if (!match) {
alert("Unable to load video data");
return;
}
let videoId = match.groups.videoId;
let p = Mordo95DL.buildParams({
video_url: 'https://v.redd.it/' + videoId + '/DASH_720.mp4?source=fallback',
audio_url: 'https://v.redd.it/' + videoId + '/DASH_audio.mp4?source=fallback',
permalink: window.location.origin + src.pendingProps.postUrl.pathname
});
window.open("https://ds.redditsave.com/download.php?" + p);
}
getReactInternalState(el) {
for (let prop of Object.keys(el)) {
if (prop.startsWith("__reactInternalInstance")) {
return el[prop];
}
}
return null;
}
returnUntil(inst, prop) {
let fInst = inst;
while (fInst != null) {
if (fInst.pendingProps[prop])
return fInst;
fInst = fInst.return;
}
return null;
}
inject() {
setInterval(() => {
let videos = document.querySelectorAll("video:not([data-tagged])");
for (let video of videos) {
if (video.parentElement.querySelector(".dlBtn") == null && video.parentElement.parentElement.firstChild.getAttribute("role") !== "slider")
this.addVideoButton(video.parentElement);
}
}, 200);
Mordo95DL.addStyle(".dlBtn{position:absolute;top:0;right:0;z-index:99999;padding:10px 15px;margin:5px;cursor:pointer;outline:0;background:#5383FB;color:white;border:1px solid 1px solid #5383FB;font-family: Segoe UI Historic, Segoe UI, Helvetica, Arial, sans-serif !important;font-size:12px;}");
Mordo95DL.addStyle(".dlBtn:hover{background-color:#86A4FC}");
}
constructor() {
this.btnText = "Download (HD)";
}
}
document.addEventListener('DOMContentLoaded', () => {
if (window.location.href.match(/youtu(\.)?be.*/)) { new YTDL().inject(); }
if (window.location.href.match(/facebook\..*/)) { new FBDL().inject(); }
if (window.location.href.match(/reddit\..*/)) { new RDDL().inject(); }
}, false);
})();
@Korb
Copy link

Korb commented Mar 11, 2022

Script not recognized as compatible with Tampermonkey 4.15.6154 (9 March 2022). Mozilla Firefox 98.0 (64-bit).

@Mordo95
Copy link
Author

Mordo95 commented Mar 11, 2022

Script not recognized as compatible with Tampermonkey 4.15.6154 (9 March 2022). Mozilla Firefox 98.0 (64-bit).

Hi Korb, I didn't test it with Firefox, I made it speficially on Chrome.

@Informatheus
Copy link

Works on Facebook.
Dont works on Youtube.
Didnt tested on Reddit.
Anyway, why dont you publish it on Greasyfork? Can I publish it (and give you credits)?

@Mordo95
Copy link
Author

Mordo95 commented Oct 18, 2022

Works on Facebook. Dont works on Youtube. Didnt tested on Reddit. Anyway, why dont you publish it on Greasyfork? Can I publish it (and give you credits)?

Yeah youtube keeps changing their player protocol so I have to put it in a different position. I also concidentially found out that the provider I use is blocked in certain countries so I will change that. And I didn't think of that, will look into publishing it there, thanks for the tip!

@Informatheus
Copy link

Informatheus commented Oct 18, 2022

Works on Facebook. Dont works on Youtube. Didnt tested on Reddit. Anyway, why dont you publish it on Greasyfork? Can I publish it (and give you credits)?

Yeah youtube keeps changing their player protocol so I have to put it in a different position. I also concidentially found out that the provider I use is blocked in certain countries so I will change that. And I didn't think of that, will look into publishing it there, thanks for the tip!

You did a good work, it will help a lot o people.

@fezzzza
Copy link

fezzzza commented Jan 13, 2023

Good job. In the case of Facebook reels, the button is not clickable. If you insert the button before the div 8 levels above the video just before the div with class=xjbqb8w...x1m6m0jg then it is positioned at a convenient point on the screen and clickable.

@Mordo95
Copy link
Author

Mordo95 commented Jan 13, 2023

Good job. In the case of Facebook reels, the button is not clickable. If you insert the button before the div 8 levels above the video just before the div with class=xjbqb8w...x1m6m0jg then it is positioned at a convenient point on the screen and clickable.

Hi, this script hasn't been updated for a while. Check out https://github.com/Mordo95/video-downloader for a more recent version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment