- // ==UserScript==
- // @name Video Downloader for Tampermonkey
- // @version 0.5
- // @description Will add a download button to various websites such as Reddit, Facebook, Youtube and Twitter
- // @author Mordo95
- // @namespace com.mordo95.Downloader
- // @license MIT
- // @match *://*/*
- // @supportURL https://github.com
- // @run-at document-start
- // @grant GM_addStyle
- // @grant GM_xmlhttpRequest
- // ==/UserScript==
-
- var __defProp = Object.defineProperty;
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
- var __publicField = (obj, key, value) => {
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
- return value;
- };
- (function() {
- var _a, _b, _c, _d;
- "use strict";
- GM_addStyle(`
- div.dlBtn {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 99999999;
- 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;
- }
- div.dlBtn:hover {
- background-color: #86A4FC;
- }div.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;
- }
- div.dlBtn:hover {
- background-color: var(--primary-button-pressed);
- }
- div.dlBtn.shorts {
- right: 110px;
- top: 5px;
- }div.dlBtn {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 99999999;
- 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;
- }
- div.dlBtn:hover {
- background-color: #86A4FC;
- } `);
- class Injector {
- constructor() {
- __publicField(this, "downloaders", []);
- }
- register(downloader) {
- if (Array.isArray(downloader)) {
- this.downloaders = this.downloaders.concat(downloader);
- } else
- this.downloaders.push(downloader);
- }
- inject(location) {
- for (const downloader of this.downloaders) {
- if (location.match(downloader.siteRegex))
- new downloader().inject();
- }
- }
- }
- const Injector$1 = new Injector();
- function staticImplements() {
- return (constructor) => {
- };
- }
- var __defProp$3 = Object.defineProperty;
- var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
- var __decorateClass$3 = (decorators, target, key, kind) => {
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
- if (decorator = decorators[i])
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
- if (kind && result)
- __defProp$3(target, key, result);
- return result;
- };
- let YoutubeDownloader = (_a = class {
- constructor() {
- __publicField(this, "btnText", "Download (HD)");
- }
- 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: 6e4,
- 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() {
- Promise.resolve().then(() => style$1);
- setInterval(() => {
- let videos = document.querySelectorAll("#ytd-player:not([data-tagged])");
- for (let video of videos) {
- video.setAttribute("data-tagged", "true");
- console.log(document.querySelector("#container"));
- this.addVideoButton(document.querySelector("#ytd-player"));
- }
- }, 200);
- }
- }, __publicField(_a, "siteRegex", /youtu(\.)?be.*/), _a);
- YoutubeDownloader = __decorateClass$3([
- staticImplements()
- ], YoutubeDownloader);
- var __defProp$2 = Object.defineProperty;
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
- var __decorateClass$2 = (decorators, target, key, kind) => {
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
- if (decorator = decorators[i])
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
- if (kind && result)
- __defProp$2(target, key, result);
- return result;
- };
- let FacebookDownloader = (_b = class {
- 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;
- }
- fiberReturnUntilFn(fiber, predicate) {
- let fiberInst = fiber;
- while (fiberInst != null) {
- if (predicate(fiberInst))
- return fiberInst;
- fiberInst = fiberInst.return;
- }
- return null;
- }
- parentsUntil(el, query) {
- let elInst = el;
- while (elInst != null) {
- if (elInst.matches(query))
- 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, isShorts = false) {
- let btn = document.createElement("div");
- btn.innerHTML = "Download (HD)";
- btn.classList.add("dlBtn");
- if (isShorts)
- btn.classList.add("shorts");
- btn.onclick = () => this.btnAct(videoEl);
- 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() {
- Promise.resolve().then(() => facebook$1);
- 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]");
- let appendTo = document.querySelector(`[data-instancekey='${props.memoizedState.memoizedState}']`);
- let isShorts = false;
- if (props.memoizedProps.subOrigin && props.memoizedProps.subOrigin === "fb_shorts_viewer") {
- let fiber2 = this.fiberReturnUntilFn(fiber, (fiber22) => {
- return fiber22.memoizedProps["data-video-id"];
- });
- let el = fiber2.stateNode.parentElement.nextSibling;
- if (el.classList.contains("__fb-dark-mode"))
- el = el.nextSibling;
- appendTo = el;
- isShorts = true;
- }
- this.addVideoButton(appendTo, video.parentElement, isShorts);
- }
- }, 200);
- }
- }, __publicField(_b, "siteRegex", /facebook\..*/), _b);
- FacebookDownloader = __decorateClass$2([
- staticImplements()
- ], FacebookDownloader);
- const Params = {
- paramsToObject(entries) {
- const result = {};
- for (const [key, value] of entries) {
- result[key] = value;
- }
- return result;
- },
- buildParams(p) {
- return new URLSearchParams(p).toString();
- }
- };
- var __defProp$1 = Object.defineProperty;
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
- var __decorateClass$1 = (decorators, target, key, kind) => {
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
- if (decorator = decorators[i])
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
- if (kind && result)
- __defProp$1(target, key, result);
- return result;
- };
- let RedditDownloader = (_c = class {
- constructor() {
- __publicField(this, "btnText", "Download (HD)");
- }
- addVideoButton(on) {
- 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);
- }
- returnUntil(inst, prop) {
- let fInst = inst;
- while (fInst != null) {
- if (fInst.pendingProps[prop])
- return fInst;
- fInst = fInst.return;
- }
- return null;
- }
- getReactInternalState(el) {
- for (let prop of Object.keys(el)) {
- if (prop.startsWith("__reactInternalInstance")) {
- return el[prop];
- }
- }
- return null;
- }
- 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 = Params.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);
- }
- inject() {
- Promise.resolve().then(() => reddit$1);
- 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);
- }
- }, __publicField(_c, "siteRegex", /reddit\..*/), _c);
- RedditDownloader = __decorateClass$1([
- staticImplements()
- ], RedditDownloader);
- var __defProp2 = Object.defineProperty;
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
- var __decorateClass = (decorators, target, key, kind) => {
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
- if (decorator = decorators[i])
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
- if (kind && result)
- __defProp2(target, key, result);
- return result;
- };
- let TwitterDownloader = (_d = class {
- constructor() {
- __publicField(this, "TWITTER_BEARER", "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA");
- }
- getReactFiber(el) {
- for (let prop of Object.keys(el)) {
- if (prop.startsWith("__reactFiber")) {
- return el[prop];
- }
- }
- return null;
- }
- parentsUntil(el, query) {
- let elInst = el;
- while (elInst != null) {
- if (elInst.matches(query))
- return elInst;
- elInst = elInst.parentElement;
- }
- return null;
- }
- fiberReturnUntil(fiber, predicate) {
- let fiberInst = fiber;
- while (fiberInst != null) {
- if (predicate(fiberInst))
- return fiberInst;
- fiberInst = fiberInst.return;
- }
- return null;
- }
- async fetchGuestToken() {
- const resp = await fetch("https://api.twitter.com/1.1/guest/activate.json", {
- method: "POST",
- headers: {
- Authorization: `Bearer ${this.TWITTER_BEARER}`
- }
- });
- const respJson = await resp.json();
- return respJson.guest_token;
- }
- async queryApi(twId) {
- const resp = await fetch(`https://api.twitter.com/2/timeline/conversation/${twId}.json`, {
- method: "GET",
- headers: {
- "Authorization": `Bearer ${this.TWITTER_BEARER}`,
- "X-Guest-Token": await this.fetchGuestToken()
- }
- });
- return await resp.json();
- }
- addVideoButton(on, videoEl) {
- let btn = document.createElement("div");
- btn.innerHTML = "Download (HD)";
- btn.classList.add("dlBtn");
- btn.onclick = () => this.btnAct(videoEl);
- on.prepend(btn);
- }
- async btnAct(videoEl) {
- const fiber = this.getReactFiber(videoEl.parentElement.parentElement);
- const fiber2 = this.fiberReturnUntil(fiber, (x) => {
- var _a2;
- return (_a2 = x.memoizedProps) == null ? void 0 : _a2.contentId;
- });
- const twId = fiber2.memoizedProps.videoId.id;
- const data = await this.queryApi(twId);
- const media = data.globalObjects.tweets[twId].extended_entities.media;
- console.log(data.globalObjects.tweets[twId], media);
- if (media.length === 0) {
- alert("Cannot fetch media data");
- }
- let variants = media[0].video_info.variants;
- variants = variants.filter((x) => x.content_type !== "application/x-mpegURL").sort((a, b) => {
- return a.bitrate > b.bitrate ? -1 : 1;
- });
- window.open(variants[0].url);
- }
- inject() {
- Promise.resolve().then(() => style$1);
- setInterval(() => {
- let videos = document.querySelectorAll("video:not([data-tagged])");
- for (let video of videos) {
- video.setAttribute("data-tagged", "true");
- this.addVideoButton(video.parentElement, video);
- }
- }, 200);
- }
- }, __publicField(_d, "siteRegex", /twitter\..*/), _d);
- TwitterDownloader = __decorateClass([
- staticImplements()
- ], TwitterDownloader);
- Injector$1.register(YoutubeDownloader);
- Injector$1.register(FacebookDownloader);
- Injector$1.register(RedditDownloader);
- Injector$1.register(TwitterDownloader);
- document.addEventListener("DOMContentLoaded", () => {
- Injector$1.inject(window.location.href);
- }, false);
- const style = "div.dlBtn {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 99999999;\n padding: 10px 15px;\n margin: 5px;\n cursor: pointer;\n outline: 0;\n background: #5383FB;\n color: white;\n border: 1px solid 1px solid #5383FB;\n font-family: Segoe UI Historic, Segoe UI, Helvetica, Arial, sans-serif !important;\n font-size: 12px;\n}\ndiv.dlBtn:hover {\n background-color: #86A4FC;\n}";
- const style$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
- __proto__: null,
- default: style
- }, Symbol.toStringTag, { value: "Module" }));
- const facebook = "div.dlBtn {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 99999;\n padding: 10px 15px;\n margin: 5px;\n cursor: pointer;\n outline: 0;\n background: var(--primary-button-background);\n color: var(--primary-button-text);\n border: 1px solid 1px solid var(--accent);\n font-family: var(--font-family-segoe) !important;\n}\ndiv.dlBtn:hover {\n background-color: var(--primary-button-pressed);\n}\ndiv.dlBtn.shorts {\n right: 110px;\n top: 5px;\n}";
- const facebook$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
- __proto__: null,
- default: facebook
- }, Symbol.toStringTag, { value: "Module" }));
- const reddit = "div.dlBtn {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 99999999;\n padding: 10px 15px;\n margin: 5px;\n cursor: pointer;\n outline: 0;\n background: #5383FB;\n color: white;\n border: 1px solid 1px solid #5383FB;\n font-family: Segoe UI Historic, Segoe UI, Helvetica, Arial, sans-serif !important;\n font-size: 12px;\n}\ndiv.dlBtn:hover {\n background-color: #86A4FC;\n}";
- const reddit$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
- __proto__: null,
- default: reddit
- }, Symbol.toStringTag, { value: "Module" }));
- })();