Facebook Videos Detector (Requires CORS disabled)

This script will help you to list all Facebook videos that were loaded during your session, click on video to generate FFMPEG download command

目前为 2024-10-31 提交的版本。查看 最新版本

// ==UserScript==
// @name         Facebook Videos Detector (Requires CORS disabled)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  This script will help you to list all Facebook videos that were loaded during your session, click on video to generate FFMPEG download command
// @author       Mahmoud Khudairi
// @match        https://www.facebook.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=facebook.com
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  "use strict";

  const loaderFunction = function () {
    const videos = document.getElementById("videos");
    const domParser = new DOMParser();
    const contents = JSON.parse(
      document.getElementById("contents").textContent
    );
    const dashURL = new URL(
      "https://www.facebook.com/video/playback/dash_mpd_debug.mpd"
    );
    function getContent(videoId, userInserted = false) {
      const videoEl = document.createElement("div");
      videoEl.className = "video";
      videos.append(videoEl);
      (async function () {
        dashURL.searchParams.set("v", videoId);
        const mpdData = await fetch(dashURL.toString()).then((res) =>
          res.text()
        );
        const mpdDom = domParser.parseFromString(mpdData, "text/xml");
        const sources = Array.from(
          mpdDom.documentElement
            .querySelector('AdaptationSet[contentType="video"]')
            .querySelectorAll("Representation")
        ).map((r) => ({
          id: r.getAttribute("id"),
          width: +r.getAttribute("width"),
          height: +r.getAttribute("height"),
          bitrate: +r.getAttribute("bandwidth"),
          frameRate: r.parentElement
            .getAttribute("frameRate")
            .split("/")
            .reduce((a, c) => a / c),
          url: r.textContent,
          initRange: r.querySelector("Initialization").getAttribute("range"),
          indexRange: r.querySelector("SegmentBase").getAttribute("indexRange"),
          firstSegmentRange: r
            .querySelector("SegmentBase")
            .getAttribute("FBFirstSegmentRange"),
          mimeType: r.getAttribute("mimeType"),
        }));
        const minSource = sources[0];
        if (!minSource) {
          videoEl.remove();

          if (userInserted) alert(`Your requested video: ${videoId} was not found`);

          return;
        }
        const maxSource = sources.at(-1);
        const segmentUrl = new URL(minSource.url);
        segmentUrl.searchParams.set("bytestart", "0");
        segmentUrl.searchParams.set(
          "byteend",
          minSource.firstSegmentRange.split("-")[1]
        );
        const video = document.createElement("video");
        video.src = segmentUrl.toString();
        await new Promise((rs) =>
          video.addEventListener("canplay", rs, { once: 1 })
        );
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");
        canvas.width = minSource.width;
        canvas.height = minSource.height;
        canvas.style.aspectRatio = `${minSource.width}/${minSource.height}`;
        const bufStart = video.buffered.start(0);
        const bufEnd = video.buffered.end(0);
        const bufDuration = bufEnd - bufStart;
        const randomPosition = bufStart + Math.random() * bufDuration;
        video.currentTime = randomPosition;
        await new Promise((rs) =>
          video.addEventListener("seeked", rs, { once: 1 })
        );
        ctx.drawImage(
          video,
          0,
          0,
          canvas.width,
          canvas.height,
          0,
          0,
          canvas.width,
          canvas.height
        );
        videoEl.append(canvas);
        const maxAudio = Array.from(
          mpdDom.documentElement
            .querySelector('AdaptationSet[contentType="audio"]')
            .querySelectorAll("Representation")
        )
          .map((r) => ({
            url: r.textContent,
            bitrate: +r.getAttribute("bandwidth"),
          }))
          .at(-1);
        const downloadCommand = ["ffmpeg"];
        downloadCommand.push("-i", `"${maxSource.url}"`);
        if (maxAudio) downloadCommand.push("-i", `"${maxAudio.url}"`);
        downloadCommand.push("-c:v", "h264", "-b:v", maxSource.bitrate);
        if (maxAudio)
          downloadCommand.push("-c:a", "aac", "-b:a", maxAudio.bitrate);
        downloadCommand.push(`${videoId}.mp4`);
        const downloadCommandText = downloadCommand.join(" ");
        videoEl.addEventListener("click", function () {
          navigator.clipboard
            .writeText(downloadCommandText)
            .then(() => alert("Commad copied!"))
            .catch(() => {
              console.log(downloadCommandText);
              alert(
                "Could not copy command, but it was printed in browser's console"
              );
            });
        });
      })();
    }
    for (const content of contents) getContent(content)
    const addbtn = document.getElementById('addbtn');
    addbtn.addEventListener("click", () => {
      const id = prompt('FB Video ID:');
      if (id) getContent(id, true);
    });
  };

  const xml = new window.XMLSerializer();
  const dom = window.document.implementation.createDocument(
    "http://www.w3.org/1999/xhtml",
    "html",
    null
  );

  const head = dom.createElement("head");
  const body = dom.createElement("body");

  const style = dom.createElement("style");
  style.innerHTML = `* {box-sizing: border-box;}
  body {
    font-family: Segoe UI;
    margin: 0 20px;
  }
  #videos {
    display: grid;
    grid-template-columns: repeat(auto-fit, 320px);
    gap: 10px;
    margin-bottom: 20px;
  }
  #videos .video {
    background-color: black;
    height: 180px;
    cursor: pointer
  }
  #videos .video canvas {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }`;
  head.append(style);

  const contents = document.createElement("script");
  contents.id = "contents";
  contents.type = "application/json";
  contents.textContent = "[]";
  head.append(contents);

  const script = document.createElement("script");
  script.src = URL.createObjectURL(
    new Blob(
      [`window.addEventListener("load", ${loaderFunction.toString()});`],
      { type: "application/javascript" }
    )
  );
  head.append(script);

  body.innerHTML =
    '<h1>Facebook Videos Detector</h1><p>Videos found on your facebook session:</p><div id="videos"></div><button id="addbtn">Add new video</button>';
  dom.firstChild.append(head, body);
  const cache = new Set();
  window.addEventListener("keyup", function (e) {
    viewing: if ((e.altKey || (e.ctrlKey && e.shiftKey)) && e.code === "KeyV") {
      e.preventDefault();
      // if (!cache.size) {
      //   this.alert("There is no videos found");
      //   break viewing;
      // }
      contents.textContent = JSON.stringify(Array.from(cache));
      dom.title = `Facebook Videos Detector - (${cache.size} video${
        cache.size > 1 ? "s" : ""
      } ${cache.size > 1 ? "were" : "was"} found)`;
      this.open(
        URL.createObjectURL(
          new Blob([xml.serializeToString(dom)], { type: dom.contentType })
        ),
        "_blank"
      );
    }
  });
  window.fetch = new Proxy(window.fetch, {
    async apply(target, arg, args) {
      const url = new URL(args[0]);
      caching: if (
        /\.mp4$/.test(url.pathname.split("/").at(-1)) &&
        url.searchParams.has("efg")
      ) {
        const efg = JSON.parse(window.atob(url.searchParams.get("efg")));
        const id = efg.video_id;
        if (cache.has(id)) break caching;
        cache.add(id);
      }
      return target.apply(arg, args);
    },
  });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址