Video Downloader for Tampermonkey

Will add a download button to various websites such as Reddit, Facebook, Youtube and Twitter

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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" }));
})();